@centrali-io/centrali-mcp 4.4.8-rc.2 → 4.4.8-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/describe.js +12 -13
- package/dist/tools/insights.js +15 -15
- package/dist/tools/pages.js +7 -7
- package/dist/tools/records.js +11 -11
- package/dist/tools/search.js +3 -10
- package/dist/tools/service-accounts.js +137 -0
- package/dist/tools/smart-queries.js +8 -8
- package/dist/tools/validation.js +15 -15
- package/package.json +1 -1
- package/src/tools/describe.ts +12 -13
- package/src/tools/insights.ts +14 -14
- package/src/tools/pages.ts +7 -7
- package/src/tools/records.ts +11 -11
- package/src/tools/search.ts +4 -12
- package/src/tools/service-accounts.ts +185 -0
- package/src/tools/smart-queries.ts +8 -8
- package/src/tools/validation.ts +14 -14
package/dist/tools/describe.js
CHANGED
|
@@ -27,10 +27,10 @@ function registerDescribeTools(server) {
|
|
|
27
27
|
type: "text",
|
|
28
28
|
text: JSON.stringify({
|
|
29
29
|
platform: "Centrali",
|
|
30
|
-
description: "Centrali is a low-code backend platform. You define data
|
|
30
|
+
description: "Centrali is a low-code backend platform. You define data collections, write compute functions, build orchestration workflows, and publish pages — all through APIs.",
|
|
31
31
|
domains: {
|
|
32
32
|
collections: {
|
|
33
|
-
summary: "Data schemas
|
|
33
|
+
summary: "Data schemas. Define the shape of your data — fields, types, constraints, references.",
|
|
34
34
|
describeWith: "describe_collections",
|
|
35
35
|
tools: [
|
|
36
36
|
"list_collections",
|
|
@@ -224,12 +224,11 @@ function registerDescribeTools(server) {
|
|
|
224
224
|
naming_guide: {
|
|
225
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
226
|
aliases: {
|
|
227
|
-
recordSlug: "Used
|
|
228
|
-
structureSlug: "Used by validation and insights tools. Same value as recordSlug.",
|
|
227
|
+
recordSlug: "Used across all tools — the collection's URL-safe identifier (e.g., 'orders').",
|
|
229
228
|
collections: "Used by search_records. Same value as recordSlug. Accepts a string or array of strings.",
|
|
230
|
-
|
|
229
|
+
collectionIds: "Used by generate_starter_pages. This is the collection UUID (not the slug). Get it from list_collections → id field.",
|
|
231
230
|
},
|
|
232
|
-
rule: "When a tool asks for recordSlug
|
|
231
|
+
rule: "When a tool asks for recordSlug or collections — use the collection's slug (e.g., 'orders'). When a tool asks for collectionIds — use the collection's UUID (from list_collections → id).",
|
|
233
232
|
},
|
|
234
233
|
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.",
|
|
235
234
|
app_credential_setup: {
|
|
@@ -294,14 +293,14 @@ function registerDescribeTools(server) {
|
|
|
294
293
|
});
|
|
295
294
|
}));
|
|
296
295
|
// ── Collections ────────────────────────────────────────────────────
|
|
297
|
-
server.tool("describe_collections", "Get the schema reference for Centrali collections
|
|
296
|
+
server.tool("describe_collections", "Get the schema reference for Centrali collections. Explains field types, property shapes, constraints, and how to define data models.", {}, () => __awaiter(this, void 0, void 0, function* () {
|
|
298
297
|
return ({
|
|
299
298
|
content: [
|
|
300
299
|
{
|
|
301
300
|
type: "text",
|
|
302
301
|
text: JSON.stringify({
|
|
303
302
|
domain: "Collections",
|
|
304
|
-
description: "Collections
|
|
303
|
+
description: "Collections define the schema for your data. Each collection has a name, a recordSlug (used in API calls), and an array of properties that define the fields.",
|
|
305
304
|
collection_shape: {
|
|
306
305
|
id: "UUID — auto-generated",
|
|
307
306
|
name: "string — display name (e.g., 'Customers')",
|
|
@@ -382,7 +381,7 @@ function registerDescribeTools(server) {
|
|
|
382
381
|
description: "Records are rows of data stored in collections. All custom field values live under the 'data' namespace.",
|
|
383
382
|
record_shape: {
|
|
384
383
|
id: "UUID — auto-generated",
|
|
385
|
-
|
|
384
|
+
collectionId: "UUID — the collection this record belongs to",
|
|
386
385
|
data: "object — all custom field values keyed by field name",
|
|
387
386
|
createdAt: "ISO 8601 datetime",
|
|
388
387
|
updatedAt: "ISO 8601 datetime",
|
|
@@ -523,7 +522,7 @@ function registerDescribeTools(server) {
|
|
|
523
522
|
"record_updated — fires after a record is modified",
|
|
524
523
|
"record_deleted — fires after a record is deleted",
|
|
525
524
|
],
|
|
526
|
-
config: "Specify the collection (
|
|
525
|
+
config: "Specify the collection (collectionId) and event type to listen for",
|
|
527
526
|
},
|
|
528
527
|
scheduled: {
|
|
529
528
|
description: "Runs on a cron schedule (e.g., every hour, daily at midnight).",
|
|
@@ -752,7 +751,7 @@ function registerDescribeTools(server) {
|
|
|
752
751
|
smart_query_shape: {
|
|
753
752
|
id: "UUID",
|
|
754
753
|
name: "string — display name",
|
|
755
|
-
|
|
754
|
+
recordSlug: "string — the collection this query targets",
|
|
756
755
|
description: "string | null",
|
|
757
756
|
query: "object — the query definition with filters, sort, fields, and aggregations",
|
|
758
757
|
variables: "object[] — declared variables with name, type, and default values",
|
|
@@ -995,7 +994,7 @@ function registerDescribeTools(server) {
|
|
|
995
994
|
description: "Centrali's AI-powered anomaly detection scans your data for unusual patterns. Insights are generated automatically or on-demand via trigger_anomaly_analysis.",
|
|
996
995
|
insight_shape: {
|
|
997
996
|
id: "UUID",
|
|
998
|
-
|
|
997
|
+
recordSlug: "string — the collection where the anomaly was detected",
|
|
999
998
|
type: "string — anomaly type (e.g., 'spike', 'drop', 'outlier', 'pattern_break')",
|
|
1000
999
|
severity: "'info' | 'warning' | 'critical'",
|
|
1001
1000
|
status: "'active' | 'acknowledged' | 'dismissed'",
|
|
@@ -1045,7 +1044,7 @@ function registerDescribeTools(server) {
|
|
|
1045
1044
|
},
|
|
1046
1045
|
suggestion_shape: {
|
|
1047
1046
|
id: "UUID",
|
|
1048
|
-
|
|
1047
|
+
recordSlug: "string — the collection containing the issue",
|
|
1049
1048
|
recordId: "UUID — the specific record with the issue",
|
|
1050
1049
|
field: "string — the field name where the issue was found",
|
|
1051
1050
|
issueType: "'typo' | 'format' | 'duplicate' | 'semantic' | 'type'",
|
package/dist/tools/insights.js
CHANGED
|
@@ -37,10 +37,10 @@ function formatError(error, context) {
|
|
|
37
37
|
}
|
|
38
38
|
function registerInsightTools(server, sdk) {
|
|
39
39
|
server.tool("list_insights", "List anomaly insights detected by Centrali's AI analysis. Insights flag unusual patterns in your data such as spikes, drops, or outliers.", {
|
|
40
|
-
|
|
40
|
+
recordSlug: zod_1.z
|
|
41
41
|
.string()
|
|
42
42
|
.optional()
|
|
43
|
-
.describe("Filter insights by
|
|
43
|
+
.describe("Filter insights by collection record slug. If omitted, returns insights for all collections."),
|
|
44
44
|
status: zod_1.z
|
|
45
45
|
.enum(["active", "acknowledged", "dismissed"])
|
|
46
46
|
.optional()
|
|
@@ -49,11 +49,11 @@ function registerInsightTools(server, sdk) {
|
|
|
49
49
|
.enum(["info", "warning", "critical"])
|
|
50
50
|
.optional()
|
|
51
51
|
.describe("Filter by severity level"),
|
|
52
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
52
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug, status, severity }) {
|
|
53
53
|
try {
|
|
54
54
|
const options = {};
|
|
55
|
-
if (
|
|
56
|
-
options.structureSlug =
|
|
55
|
+
if (recordSlug)
|
|
56
|
+
options.structureSlug = recordSlug;
|
|
57
57
|
if (status)
|
|
58
58
|
options.status = status;
|
|
59
59
|
if (severity)
|
|
@@ -127,14 +127,14 @@ function registerInsightTools(server, sdk) {
|
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
129
|
}));
|
|
130
|
-
server.tool("get_insights_summary", "Get a summary of anomaly insights in the workspace — counts by status and severity. Optionally filter by
|
|
131
|
-
|
|
130
|
+
server.tool("get_insights_summary", "Get a summary of anomaly insights in the workspace — counts by status and severity. Optionally filter by collection.", {
|
|
131
|
+
recordSlug: zod_1.z
|
|
132
132
|
.string()
|
|
133
133
|
.optional()
|
|
134
|
-
.describe("Filter summary to a specific
|
|
135
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
134
|
+
.describe("Filter summary to a specific collection's record slug. If omitted, returns workspace-wide summary."),
|
|
135
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug }) {
|
|
136
136
|
try {
|
|
137
|
-
const result = yield sdk.anomalyInsights.getSummary(
|
|
137
|
+
const result = yield sdk.anomalyInsights.getSummary(recordSlug);
|
|
138
138
|
return {
|
|
139
139
|
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
140
140
|
};
|
|
@@ -148,11 +148,11 @@ function registerInsightTools(server, sdk) {
|
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
}));
|
|
151
|
-
server.tool("trigger_anomaly_analysis", "Trigger AI-powered anomaly analysis for a
|
|
152
|
-
|
|
153
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
151
|
+
server.tool("trigger_anomaly_analysis", "Trigger AI-powered anomaly analysis for a collection. Starts a background analysis to detect unusual patterns. Check list_insights after the scan completes.", {
|
|
152
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug to analyze"),
|
|
153
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug }) {
|
|
154
154
|
try {
|
|
155
|
-
const result = yield sdk.anomalyInsights.triggerAnalysis(
|
|
155
|
+
const result = yield sdk.anomalyInsights.triggerAnalysis(recordSlug);
|
|
156
156
|
return {
|
|
157
157
|
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
158
158
|
};
|
|
@@ -162,7 +162,7 @@ function registerInsightTools(server, sdk) {
|
|
|
162
162
|
content: [
|
|
163
163
|
{
|
|
164
164
|
type: "text",
|
|
165
|
-
text: formatError(error, `triggering anomaly analysis for '${
|
|
165
|
+
text: formatError(error, `triggering anomaly analysis for '${recordSlug}'`),
|
|
166
166
|
},
|
|
167
167
|
],
|
|
168
168
|
isError: true,
|
package/dist/tools/pages.js
CHANGED
|
@@ -117,7 +117,7 @@ function registerPageTools(server, sdk, centraliUrl, workspaceId) {
|
|
|
117
117
|
const { client } = createPagesClient(sdk, centraliUrl, workspaceId);
|
|
118
118
|
const basePath = `/ws/${workspaceId}/api/v1/pages`;
|
|
119
119
|
// ── Page CRUD ──────────────────────────────────────────────────────
|
|
120
|
-
server.tool("list_pages", "List all pages in the workspace. Pages are pre-built UI views (lists, detail views, forms, dashboards) that surface data from
|
|
120
|
+
server.tool("list_pages", "List all pages in the workspace. Pages are pre-built UI views (lists, detail views, forms, dashboards) that surface data from collections.", {
|
|
121
121
|
page: zod_1.z.number().optional().describe("Page number (1-indexed, default: 1)"),
|
|
122
122
|
pageSize: zod_1.z.number().optional().describe("Items per page (default: 20)"),
|
|
123
123
|
pageType: zod_1.z
|
|
@@ -161,7 +161,7 @@ function registerPageTools(server, sdk, centraliUrl, workspaceId) {
|
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
163
|
}));
|
|
164
|
-
server.tool("create_page", `Create a new page in the workspace. A page is a UI view backed by data from
|
|
164
|
+
server.tool("create_page", `Create a new page in the workspace. A page is a UI view backed by data from collections. Specify the page type: 'list' for data tables, 'detail' for single-record views, 'form' for data entry, 'dashboard' for metrics and charts.
|
|
165
165
|
|
|
166
166
|
Navigate-to-page actions can use config.useQueryParams: true to pass selected row fields as URL query params to the target page. Pair with paramConfig: { source: 'row', mode: 'selected', selectedFields: ['id'] } to control which fields are passed. Use config.paramMapping: { sourceField: 'targetParam' } to rename fields in the URL (e.g., { requestId: 'id' } passes ?id=<value> instead of ?requestId=<value>). The target detail page can then use variable bindings with { source: 'url', param: 'id' } to read those params.`, {
|
|
167
167
|
name: zod_1.z.string().describe("Display name for the page (e.g., 'Customer List')"),
|
|
@@ -474,17 +474,17 @@ Common patterns:
|
|
|
474
474
|
}
|
|
475
475
|
}));
|
|
476
476
|
// ── Assembly (AI-assisted page generation) ────────────────────────
|
|
477
|
-
server.tool("generate_starter_pages", "Generate page proposals from
|
|
478
|
-
|
|
477
|
+
server.tool("generate_starter_pages", "Generate page proposals from collection IDs. Uses guided assembly to create page definitions (list, detail, form, dashboard) tailored to the data schema. Returns proposals that can be reviewed and accepted.", {
|
|
478
|
+
collectionIds: zod_1.z
|
|
479
479
|
.array(zod_1.z.string())
|
|
480
|
-
.describe("Array of
|
|
480
|
+
.describe("Array of collection IDs (UUIDs) to generate pages for. Get these from list_collections → id field."),
|
|
481
481
|
goals: zod_1.z
|
|
482
482
|
.array(zod_1.z.string())
|
|
483
483
|
.optional()
|
|
484
484
|
.describe("Optional goals to guide page generation (e.g., 'customer management dashboard', 'order tracking')"),
|
|
485
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
485
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ collectionIds, goals }) {
|
|
486
486
|
try {
|
|
487
|
-
const body = { structureIds };
|
|
487
|
+
const body = { structureIds: collectionIds };
|
|
488
488
|
if (goals)
|
|
489
489
|
body.goals = goals;
|
|
490
490
|
const resp = yield client.post(`${basePath}/generate-starter-pages`, body);
|
package/dist/tools/records.js
CHANGED
|
@@ -34,10 +34,10 @@ function formatError(error, context) {
|
|
|
34
34
|
return `Error ${context}: ${error instanceof Error ? error.message : String(error)}`;
|
|
35
35
|
}
|
|
36
36
|
function registerRecordTools(server, sdk) {
|
|
37
|
-
server.tool("query_records", "Query records from a
|
|
37
|
+
server.tool("query_records", "Query records from a collection with optional filters, sorting, and pagination. Filters use 'data.' prefix for custom fields and bracket notation for operators (e.g., 'data.status': 'active', 'data.price[lte]': 100).", {
|
|
38
38
|
recordSlug: zod_1.z
|
|
39
39
|
.string()
|
|
40
|
-
.describe("The
|
|
40
|
+
.describe("The collection's record slug (e.g., 'orders')"),
|
|
41
41
|
filters: zod_1.z
|
|
42
42
|
.record(zod_1.z.string(), zod_1.z.any())
|
|
43
43
|
.optional()
|
|
@@ -82,8 +82,8 @@ function registerRecordTools(server, sdk) {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
}));
|
|
85
|
-
server.tool("get_record", "Get a single record by its ID from a
|
|
86
|
-
recordSlug: zod_1.z.string().describe("The
|
|
85
|
+
server.tool("get_record", "Get a single record by its ID from a collection. Optionally expand reference fields.", {
|
|
86
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug"),
|
|
87
87
|
id: zod_1.z.string().describe("The record ID (UUID)"),
|
|
88
88
|
expand: zod_1.z
|
|
89
89
|
.string()
|
|
@@ -111,10 +111,10 @@ function registerRecordTools(server, sdk) {
|
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
}));
|
|
114
|
-
server.tool("create_record", "Create a new record in a
|
|
114
|
+
server.tool("create_record", "Create a new record in a collection. Pass the record data as a JSON object with field names matching the collection's properties.", {
|
|
115
115
|
recordSlug: zod_1.z
|
|
116
116
|
.string()
|
|
117
|
-
.describe("The
|
|
117
|
+
.describe("The collection's record slug (e.g., 'orders')"),
|
|
118
118
|
data: zod_1.z
|
|
119
119
|
.record(zod_1.z.string(), zod_1.z.any())
|
|
120
120
|
.describe("The record data object (field names as keys)"),
|
|
@@ -140,7 +140,7 @@ function registerRecordTools(server, sdk) {
|
|
|
140
140
|
}
|
|
141
141
|
}));
|
|
142
142
|
server.tool("update_record", "Update an existing record by ID. Only include the fields you want to change.", {
|
|
143
|
-
recordSlug: zod_1.z.string().describe("The
|
|
143
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug"),
|
|
144
144
|
id: zod_1.z.string().describe("The record ID (UUID) to update"),
|
|
145
145
|
data: zod_1.z
|
|
146
146
|
.record(zod_1.z.string(), zod_1.z.any())
|
|
@@ -167,7 +167,7 @@ function registerRecordTools(server, sdk) {
|
|
|
167
167
|
}
|
|
168
168
|
}));
|
|
169
169
|
server.tool("delete_record", "Delete a record by ID. Performs a soft delete by default (can be restored). Set hard=true for permanent deletion.", {
|
|
170
|
-
recordSlug: zod_1.z.string().describe("The
|
|
170
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug"),
|
|
171
171
|
id: zod_1.z.string().describe("The record ID (UUID) to delete"),
|
|
172
172
|
hard: zod_1.z
|
|
173
173
|
.boolean()
|
|
@@ -200,7 +200,7 @@ function registerRecordTools(server, sdk) {
|
|
|
200
200
|
}
|
|
201
201
|
}));
|
|
202
202
|
server.tool("upsert_record", "Create or update a record atomically. Matches on the provided fields to find an existing record — updates it if found, creates it if not. Returns the record and whether it was 'created' or 'updated'.", {
|
|
203
|
-
recordSlug: zod_1.z.string().describe("The
|
|
203
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug"),
|
|
204
204
|
match: zod_1.z
|
|
205
205
|
.record(zod_1.z.string(), zod_1.z.any())
|
|
206
206
|
.describe("Fields to match on when looking for an existing record (e.g. { sku: 'WIDGET-001' })"),
|
|
@@ -229,7 +229,7 @@ function registerRecordTools(server, sdk) {
|
|
|
229
229
|
}
|
|
230
230
|
}));
|
|
231
231
|
server.tool("get_records_by_ids", "Fetch multiple records by their IDs in a single request. Returns an array of records.", {
|
|
232
|
-
recordSlug: zod_1.z.string().describe("The
|
|
232
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug"),
|
|
233
233
|
ids: zod_1.z.array(zod_1.z.string()).describe("Array of record IDs (UUIDs) to fetch"),
|
|
234
234
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug, ids }) {
|
|
235
235
|
try {
|
|
@@ -248,7 +248,7 @@ function registerRecordTools(server, sdk) {
|
|
|
248
248
|
}
|
|
249
249
|
}));
|
|
250
250
|
server.tool("restore_record", "Restore a soft-deleted record by ID. Only works on records deleted without hard=true.", {
|
|
251
|
-
recordSlug: zod_1.z.string().describe("The
|
|
251
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug"),
|
|
252
252
|
id: zod_1.z.string().describe("The record ID (UUID) to restore"),
|
|
253
253
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug, id }) {
|
|
254
254
|
try {
|
package/dist/tools/search.js
CHANGED
|
@@ -34,29 +34,22 @@ function formatError(error, context) {
|
|
|
34
34
|
return `Error ${context}: ${error instanceof Error ? error.message : String(error)}`;
|
|
35
35
|
}
|
|
36
36
|
function registerSearchTools(server, sdk) {
|
|
37
|
-
server.tool("search_records", "Full-text search across records in the workspace. Powered by Meilisearch. Optionally filter by
|
|
37
|
+
server.tool("search_records", "Full-text search across records in the workspace. Powered by Meilisearch. Optionally filter by collection(s) and limit results.", {
|
|
38
38
|
query: zod_1.z.string().describe("The search query string"),
|
|
39
39
|
collections: zod_1.z
|
|
40
40
|
.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())])
|
|
41
41
|
.optional()
|
|
42
|
-
.describe("Filter by collection slug(s). Single slug string or array of slugs"),
|
|
43
|
-
structures: zod_1.z
|
|
44
|
-
.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())])
|
|
45
|
-
.optional()
|
|
46
|
-
.describe("[DEPRECATED: use collections instead] Filter by structure slug(s). Single slug string or array of slugs"),
|
|
42
|
+
.describe("Filter by collection record slug(s). Single slug string or array of slugs"),
|
|
47
43
|
limit: zod_1.z
|
|
48
44
|
.number()
|
|
49
45
|
.optional()
|
|
50
46
|
.describe("Maximum results to return (default: 20, max: 100)"),
|
|
51
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({ query, collections,
|
|
47
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ query, collections, limit }) {
|
|
52
48
|
try {
|
|
53
49
|
const options = {};
|
|
54
50
|
if (collections) {
|
|
55
51
|
options.collections = collections;
|
|
56
52
|
}
|
|
57
|
-
else if (structures) {
|
|
58
|
-
options.collections = structures;
|
|
59
|
-
}
|
|
60
53
|
if (limit)
|
|
61
54
|
options.limit = limit;
|
|
62
55
|
const result = yield sdk.search(query, Object.keys(options).length > 0 ? options : undefined);
|
|
@@ -853,4 +853,141 @@ function registerServiceAccountTools(server, sdk, centraliUrl, workspaceId, ownC
|
|
|
853
853
|
};
|
|
854
854
|
}
|
|
855
855
|
}));
|
|
856
|
+
// ── Policies CRUD ──────────────────────────────────────────────────
|
|
857
|
+
// These tools give the MCP the ability to create, inspect, and delete
|
|
858
|
+
// access policies directly — making remediation safely reversible.
|
|
859
|
+
const getPoliciesClient = () => createIamClient(sdk, centraliUrl, workspaceId, "access/policies");
|
|
860
|
+
const getPermissionsClient = () => createIamClient(sdk, centraliUrl, workspaceId, "access/permissions");
|
|
861
|
+
const getResourcesClient = () => createIamClient(sdk, centraliUrl, workspaceId, "access/resources");
|
|
862
|
+
server.tool("list_policies", "List all access control policies in the workspace. Policies define who can do what — they bind roles/groups/principals to permissions with optional conditions.", {
|
|
863
|
+
page: zod_1.z.number().optional().describe("Page number (default: 1)"),
|
|
864
|
+
pageSize: zod_1.z.number().optional().describe("Results per page (default: 20)"),
|
|
865
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ page, pageSize }) {
|
|
866
|
+
try {
|
|
867
|
+
const params = {};
|
|
868
|
+
if (page !== undefined)
|
|
869
|
+
params.page = page;
|
|
870
|
+
if (pageSize !== undefined)
|
|
871
|
+
params.pageSize = pageSize;
|
|
872
|
+
const result = yield getPoliciesClient().get("/", { params });
|
|
873
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
874
|
+
}
|
|
875
|
+
catch (error) {
|
|
876
|
+
return { content: [{ type: "text", text: formatError(error, "listing policies") }], isError: true };
|
|
877
|
+
}
|
|
878
|
+
}));
|
|
879
|
+
server.tool("get_policy", "Get the full definition of an access control policy by ID.", {
|
|
880
|
+
policyId: zod_1.z.string().describe("The policy ID"),
|
|
881
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ policyId }) {
|
|
882
|
+
try {
|
|
883
|
+
const result = yield getPoliciesClient().get(`/${policyId}`);
|
|
884
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
return { content: [{ type: "text", text: formatError(error, `getting policy '${policyId}'`) }], isError: true };
|
|
888
|
+
}
|
|
889
|
+
}));
|
|
890
|
+
server.tool("create_policy", "Create an access control policy. Policies grant or deny actions on resources to principals (users, service accounts, groups, roles).", {
|
|
891
|
+
policy: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).describe("The policy definition object. Must include: name, effect ('allow'|'deny'), principals, resources, actions. May include conditions."),
|
|
892
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ policy }) {
|
|
893
|
+
try {
|
|
894
|
+
const result = yield getPoliciesClient().post("/", policy);
|
|
895
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
return { content: [{ type: "text", text: formatError(error, "creating policy") }], isError: true };
|
|
899
|
+
}
|
|
900
|
+
}));
|
|
901
|
+
server.tool("update_policy", "Update an existing access control policy by ID.", {
|
|
902
|
+
policyId: zod_1.z.string().describe("The policy ID to update"),
|
|
903
|
+
policy: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).describe("The updated policy definition"),
|
|
904
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ policyId, policy }) {
|
|
905
|
+
try {
|
|
906
|
+
const result = yield getPoliciesClient().put(`/${policyId}`, policy);
|
|
907
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
908
|
+
}
|
|
909
|
+
catch (error) {
|
|
910
|
+
return { content: [{ type: "text", text: formatError(error, `updating policy '${policyId}'`) }], isError: true };
|
|
911
|
+
}
|
|
912
|
+
}));
|
|
913
|
+
server.tool("delete_policy", "Delete an access control policy by ID. This immediately revokes the access it granted. Use this to undo apply_remediation.", {
|
|
914
|
+
policyId: zod_1.z.string().describe("The policy ID to delete"),
|
|
915
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ policyId }) {
|
|
916
|
+
try {
|
|
917
|
+
yield getPoliciesClient().delete(`/${policyId}`);
|
|
918
|
+
return { content: [{ type: "text", text: `Policy '${policyId}' deleted.` }] };
|
|
919
|
+
}
|
|
920
|
+
catch (error) {
|
|
921
|
+
return { content: [{ type: "text", text: formatError(error, `deleting policy '${policyId}'`) }], isError: true };
|
|
922
|
+
}
|
|
923
|
+
}));
|
|
924
|
+
// ── Permissions CRUD ───────────────────────────────────────────────
|
|
925
|
+
server.tool("list_permissions", "List all permission definitions in the workspace. Permissions are resource + action pairs (e.g., 'workspace::records' + 'create').", {
|
|
926
|
+
page: zod_1.z.number().optional().describe("Page number (default: 1)"),
|
|
927
|
+
pageSize: zod_1.z.number().optional().describe("Results per page (default: 20)"),
|
|
928
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ page, pageSize }) {
|
|
929
|
+
try {
|
|
930
|
+
const params = {};
|
|
931
|
+
if (page !== undefined)
|
|
932
|
+
params.page = page;
|
|
933
|
+
if (pageSize !== undefined)
|
|
934
|
+
params.pageSize = pageSize;
|
|
935
|
+
const result = yield getPermissionsClient().get("/", { params });
|
|
936
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
937
|
+
}
|
|
938
|
+
catch (error) {
|
|
939
|
+
return { content: [{ type: "text", text: formatError(error, "listing permissions") }], isError: true };
|
|
940
|
+
}
|
|
941
|
+
}));
|
|
942
|
+
server.tool("create_permission", "Create a new permission definition (resource + action pair).", {
|
|
943
|
+
permission: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).describe("The permission definition. Must include: resource (name), action."),
|
|
944
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ permission }) {
|
|
945
|
+
try {
|
|
946
|
+
const result = yield getPermissionsClient().post("/", permission);
|
|
947
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
return { content: [{ type: "text", text: formatError(error, "creating permission") }], isError: true };
|
|
951
|
+
}
|
|
952
|
+
}));
|
|
953
|
+
server.tool("delete_permission", "Delete a permission definition by ID.", {
|
|
954
|
+
permissionId: zod_1.z.string().describe("The permission ID to delete"),
|
|
955
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ permissionId }) {
|
|
956
|
+
try {
|
|
957
|
+
yield getPermissionsClient().delete(`/${permissionId}`);
|
|
958
|
+
return { content: [{ type: "text", text: `Permission '${permissionId}' deleted.` }] };
|
|
959
|
+
}
|
|
960
|
+
catch (error) {
|
|
961
|
+
return { content: [{ type: "text", text: formatError(error, `deleting permission '${permissionId}'`) }], isError: true };
|
|
962
|
+
}
|
|
963
|
+
}));
|
|
964
|
+
// ── Resources ──────────────────────────────────────────────────────
|
|
965
|
+
server.tool("list_resources", "List all protected resource definitions in the workspace. Resources are the things permissions act on (e.g., 'workspace::records', 'workspace::functions').", {
|
|
966
|
+
page: zod_1.z.number().optional().describe("Page number (default: 1)"),
|
|
967
|
+
pageSize: zod_1.z.number().optional().describe("Results per page (default: 20)"),
|
|
968
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ page, pageSize }) {
|
|
969
|
+
try {
|
|
970
|
+
const params = {};
|
|
971
|
+
if (page !== undefined)
|
|
972
|
+
params.page = page;
|
|
973
|
+
if (pageSize !== undefined)
|
|
974
|
+
params.pageSize = pageSize;
|
|
975
|
+
const result = yield getResourcesClient().get("/", { params });
|
|
976
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
return { content: [{ type: "text", text: formatError(error, "listing resources") }], isError: true };
|
|
980
|
+
}
|
|
981
|
+
}));
|
|
982
|
+
server.tool("get_resource", "Get details of a protected resource definition by ID.", {
|
|
983
|
+
resourceId: zod_1.z.string().describe("The resource ID"),
|
|
984
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ resourceId }) {
|
|
985
|
+
try {
|
|
986
|
+
const result = yield getResourcesClient().get(`/${resourceId}`);
|
|
987
|
+
return { content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }] };
|
|
988
|
+
}
|
|
989
|
+
catch (error) {
|
|
990
|
+
return { content: [{ type: "text", text: formatError(error, `getting resource '${resourceId}'`) }], isError: true };
|
|
991
|
+
}
|
|
992
|
+
}));
|
|
856
993
|
}
|
|
@@ -34,11 +34,11 @@ function formatError(error, context) {
|
|
|
34
34
|
return `Error ${context}: ${error instanceof Error ? error.message : String(error)}`;
|
|
35
35
|
}
|
|
36
36
|
function registerSmartQueryTools(server, sdk) {
|
|
37
|
-
server.tool("list_smart_queries", "List smart queries. Smart queries are reusable, parameterized queries defined in the Centrali console. Optionally filter by
|
|
37
|
+
server.tool("list_smart_queries", "List smart queries. Smart queries are reusable, parameterized queries defined in the Centrali console. Optionally filter by collection record slug.", {
|
|
38
38
|
recordSlug: zod_1.z
|
|
39
39
|
.string()
|
|
40
40
|
.optional()
|
|
41
|
-
.describe("Filter by
|
|
41
|
+
.describe("Filter by collection record slug. If omitted, lists all smart queries in the workspace"),
|
|
42
42
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug }) {
|
|
43
43
|
try {
|
|
44
44
|
const result = recordSlug
|
|
@@ -65,7 +65,7 @@ function registerSmartQueryTools(server, sdk) {
|
|
|
65
65
|
server.tool("execute_smart_query", "Execute a smart query by ID and return the results. Smart queries can have parameterized variables using {{variableName}} syntax.", {
|
|
66
66
|
recordSlug: zod_1.z
|
|
67
67
|
.string()
|
|
68
|
-
.describe("The
|
|
68
|
+
.describe("The collection's record slug the query belongs to"),
|
|
69
69
|
queryId: zod_1.z.string().describe("The smart query ID (UUID) to execute"),
|
|
70
70
|
variables: zod_1.z
|
|
71
71
|
.record(zod_1.z.string(), zod_1.z.string())
|
|
@@ -94,7 +94,7 @@ function registerSmartQueryTools(server, sdk) {
|
|
|
94
94
|
}
|
|
95
95
|
}));
|
|
96
96
|
server.tool("get_smart_query", "Get a smart query by ID. Returns the full query definition including filters, sort, and variable declarations.", {
|
|
97
|
-
recordSlug: zod_1.z.string().describe("The
|
|
97
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug the query belongs to"),
|
|
98
98
|
queryId: zod_1.z.string().describe("The smart query ID (UUID)"),
|
|
99
99
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug, queryId }) {
|
|
100
100
|
try {
|
|
@@ -118,7 +118,7 @@ function registerSmartQueryTools(server, sdk) {
|
|
|
118
118
|
}
|
|
119
119
|
}));
|
|
120
120
|
server.tool("create_smart_query", "Create a new smart query for a collection. Smart queries are reusable, parameterized queries with filter, sort, and variable support.", {
|
|
121
|
-
recordSlug: zod_1.z.string().describe("The
|
|
121
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug to create the query for"),
|
|
122
122
|
name: zod_1.z.string().describe("Display name for the smart query"),
|
|
123
123
|
description: zod_1.z.string().optional().describe("Optional description"),
|
|
124
124
|
queryDefinition: zod_1.z
|
|
@@ -149,7 +149,7 @@ function registerSmartQueryTools(server, sdk) {
|
|
|
149
149
|
}
|
|
150
150
|
}));
|
|
151
151
|
server.tool("update_smart_query", "Update an existing smart query. Only include the fields you want to change.", {
|
|
152
|
-
recordSlug: zod_1.z.string().describe("The
|
|
152
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug the query belongs to"),
|
|
153
153
|
queryId: zod_1.z.string().describe("The smart query ID (UUID) to update"),
|
|
154
154
|
name: zod_1.z.string().optional().describe("Updated display name"),
|
|
155
155
|
description: zod_1.z.string().optional().describe("Updated description"),
|
|
@@ -186,7 +186,7 @@ function registerSmartQueryTools(server, sdk) {
|
|
|
186
186
|
}
|
|
187
187
|
}));
|
|
188
188
|
server.tool("delete_smart_query", "Delete a smart query by ID.", {
|
|
189
|
-
recordSlug: zod_1.z.string().describe("The
|
|
189
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug the query belongs to"),
|
|
190
190
|
queryId: zod_1.z.string().describe("The smart query ID (UUID) to delete"),
|
|
191
191
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug, queryId }) {
|
|
192
192
|
try {
|
|
@@ -213,7 +213,7 @@ function registerSmartQueryTools(server, sdk) {
|
|
|
213
213
|
}
|
|
214
214
|
}));
|
|
215
215
|
server.tool("test_smart_query", "Test execute a query definition without saving it. Useful for validating query syntax and previewing results before creating a smart query.", {
|
|
216
|
-
recordSlug: zod_1.z.string().describe("The
|
|
216
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug to test against"),
|
|
217
217
|
queryDefinition: zod_1.z
|
|
218
218
|
.record(zod_1.z.string(), zod_1.z.any())
|
|
219
219
|
.describe("The query definition to test (where, sort, limit, select, etc.)"),
|
package/dist/tools/validation.js
CHANGED
|
@@ -36,16 +36,16 @@ function formatError(error, context) {
|
|
|
36
36
|
return `Error ${context}: ${error instanceof Error ? error.message : String(error)}`;
|
|
37
37
|
}
|
|
38
38
|
function registerValidationTools(server, sdk) {
|
|
39
|
-
server.tool("trigger_validation_scan", "Trigger an AI-powered data quality scan on a
|
|
40
|
-
|
|
39
|
+
server.tool("trigger_validation_scan", "Trigger an AI-powered data quality scan on a collection. Detects typos, format inconsistencies, duplicates, and other data issues. Returns a batchId — use get_validation_summary or list_validation_suggestions to see results after the scan completes.", {
|
|
40
|
+
recordSlug: zod_1.z.string().describe("The collection's record slug to scan"),
|
|
41
41
|
validationTypes: zod_1.z
|
|
42
42
|
.array(zod_1.z.enum(["typo", "format", "duplicate", "semantic", "type"]))
|
|
43
43
|
.optional()
|
|
44
44
|
.describe("Specific validation types to run. If omitted, all types are run."),
|
|
45
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
45
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug, validationTypes }) {
|
|
46
46
|
try {
|
|
47
47
|
const options = validationTypes ? { validationTypes } : undefined;
|
|
48
|
-
const result = yield sdk.validation.triggerScan(
|
|
48
|
+
const result = yield sdk.validation.triggerScan(recordSlug, options);
|
|
49
49
|
return {
|
|
50
50
|
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
51
51
|
};
|
|
@@ -55,7 +55,7 @@ function registerValidationTools(server, sdk) {
|
|
|
55
55
|
content: [
|
|
56
56
|
{
|
|
57
57
|
type: "text",
|
|
58
|
-
text: formatError(error, `triggering validation scan for '${
|
|
58
|
+
text: formatError(error, `triggering validation scan for '${recordSlug}'`),
|
|
59
59
|
},
|
|
60
60
|
],
|
|
61
61
|
isError: true,
|
|
@@ -63,10 +63,10 @@ function registerValidationTools(server, sdk) {
|
|
|
63
63
|
}
|
|
64
64
|
}));
|
|
65
65
|
server.tool("list_validation_suggestions", "List data quality suggestions generated by validation scans. Each suggestion identifies an issue in a record and proposes a fix.", {
|
|
66
|
-
|
|
66
|
+
recordSlug: zod_1.z
|
|
67
67
|
.string()
|
|
68
68
|
.optional()
|
|
69
|
-
.describe("Filter suggestions to a specific
|
|
69
|
+
.describe("Filter suggestions to a specific collection's record slug"),
|
|
70
70
|
status: zod_1.z
|
|
71
71
|
.enum(["pending", "accepted", "rejected", "auto-applied"])
|
|
72
72
|
.optional()
|
|
@@ -79,11 +79,11 @@ function registerValidationTools(server, sdk) {
|
|
|
79
79
|
.number()
|
|
80
80
|
.optional()
|
|
81
81
|
.describe("Minimum confidence score (0-1). Use 0.9 for high-confidence suggestions only."),
|
|
82
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
82
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug, status, issueType, minConfidence }) {
|
|
83
83
|
try {
|
|
84
84
|
const options = {};
|
|
85
|
-
if (
|
|
86
|
-
options.structureSlug =
|
|
85
|
+
if (recordSlug)
|
|
86
|
+
options.structureSlug = recordSlug;
|
|
87
87
|
if (status)
|
|
88
88
|
options.status = status;
|
|
89
89
|
if (issueType)
|
|
@@ -107,14 +107,14 @@ function registerValidationTools(server, sdk) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
}));
|
|
110
|
-
server.tool("get_validation_summary", "Get a summary of data quality across the workspace — counts of pending, accepted, and rejected suggestions. Optionally filter by
|
|
111
|
-
|
|
110
|
+
server.tool("get_validation_summary", "Get a summary of data quality across the workspace — counts of pending, accepted, and rejected suggestions. Optionally filter by collection.", {
|
|
111
|
+
recordSlug: zod_1.z
|
|
112
112
|
.string()
|
|
113
113
|
.optional()
|
|
114
|
-
.describe("Filter summary to a specific
|
|
115
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
114
|
+
.describe("Filter summary to a specific collection's record slug. If omitted, returns workspace-wide summary."),
|
|
115
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ recordSlug }) {
|
|
116
116
|
try {
|
|
117
|
-
const result = yield sdk.validation.getSummary(
|
|
117
|
+
const result = yield sdk.validation.getSummary(recordSlug);
|
|
118
118
|
return {
|
|
119
119
|
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
120
120
|
};
|