@centrali-io/centrali-mcp 4.4.8-rc.7 → 4.4.8-rc.9
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 +52 -18
- package/dist/tools/service-accounts.js +16 -28
- package/package.json +1 -1
- package/src/tools/describe.ts +52 -18
- package/src/tools/service-accounts.ts +16 -24
package/dist/tools/describe.js
CHANGED
|
@@ -338,18 +338,46 @@ function registerDescribeTools(server) {
|
|
|
338
338
|
},
|
|
339
339
|
render_url: {
|
|
340
340
|
method: "client.getFileRenderUrl(renderId, options?)",
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
important: "uploadFile() returns a renderId (string), NOT a URL. You MUST call getFileRenderUrl(renderId) to construct the actual URL for display.",
|
|
342
|
+
url_pattern: "https://api.{domain}/storage/ws/{workspaceId}/api/v1/render/{renderId}",
|
|
343
|
+
transforms: "Optional image transformations: width, height, fit ('cover'|'contain'|'fill'), format ('webp'|'png'|'jpeg'), quality (1-100).",
|
|
344
|
+
public_vs_private: {
|
|
345
|
+
public: "If uploaded with isPublic=true, the render URL works without any auth — use directly in <img> tags.",
|
|
346
|
+
private: "If uploaded with isPublic=false (default), the render URL requires authentication. Pass a Bearer token (service account) or x-api-key (publishable key) in the request headers. For browser rendering of private images, use a server-side proxy route or a publishable key.",
|
|
347
|
+
},
|
|
348
|
+
example: [
|
|
349
|
+
"// Upload returns renderId, NOT a URL",
|
|
350
|
+
"const { data: renderId } = await centrali.uploadFile(file, '/root/shared/logos', true);",
|
|
351
|
+
"",
|
|
352
|
+
"// Build the render URL from the renderId",
|
|
353
|
+
"const url = centrali.getFileRenderUrl(renderId);",
|
|
354
|
+
"// => 'https://api.centrali.io/storage/ws/my-workspace/api/v1/render/abc123'",
|
|
355
|
+
"",
|
|
356
|
+
"// With image transforms (thumbnail)",
|
|
357
|
+
"const thumbUrl = centrali.getFileRenderUrl(renderId, { width: 200, height: 200, fit: 'cover', format: 'webp' });",
|
|
358
|
+
"",
|
|
359
|
+
"// For private images in Next.js — proxy through an API route",
|
|
360
|
+
"// app/api/image/[renderId]/route.ts",
|
|
361
|
+
"export async function GET(req, { params }) {",
|
|
362
|
+
" const centrali = new CentraliSDK({ baseUrl, workspaceId, clientId, clientSecret });",
|
|
363
|
+
" const url = centrali.getFileRenderUrl(params.renderId);",
|
|
364
|
+
" const resp = await fetch(url, {",
|
|
365
|
+
" headers: { Authorization: `Bearer ${await centrali.getToken()}` }",
|
|
366
|
+
" });",
|
|
367
|
+
" return new Response(resp.body, { headers: { 'Content-Type': resp.headers.get('Content-Type') } });",
|
|
368
|
+
"}",
|
|
369
|
+
].join("\n"),
|
|
343
370
|
},
|
|
344
371
|
download_url: {
|
|
345
372
|
method: "client.getFileDownloadUrl(renderId)",
|
|
346
|
-
note: "Returns a URL that triggers a file download.",
|
|
373
|
+
note: "Returns a URL that triggers a file download. Same auth rules as render URLs.",
|
|
347
374
|
},
|
|
348
375
|
tips: [
|
|
349
376
|
"/root/shared always exists — upload there directly or create subfolders like /root/shared/logos",
|
|
350
377
|
"Set isPublic=true for files that need to be accessible without auth (logos, avatars, public images)",
|
|
351
|
-
"Store the renderId
|
|
378
|
+
"Store the renderId on a record field — then use getFileRenderUrl() to build the URL when displaying",
|
|
352
379
|
"Use image transformations for thumbnails instead of uploading multiple sizes",
|
|
380
|
+
"For private images in browser apps: either use a server-side proxy route or a publishable key with storage scope",
|
|
353
381
|
],
|
|
354
382
|
},
|
|
355
383
|
realtime: {
|
|
@@ -2188,23 +2216,29 @@ function registerDescribeTools(server) {
|
|
|
2188
2216
|
createdAt: "string — ISO 8601 timestamp",
|
|
2189
2217
|
},
|
|
2190
2218
|
permission_model: {
|
|
2191
|
-
description: "
|
|
2219
|
+
description: "Access control is attribute-based. The caller's groups, roles, and other attributes (from JWT claims) are evaluated against policy conditions using JSON-Logic.",
|
|
2220
|
+
evaluation_flow: "Request → extract groups/roles from JWT → find permissions for the requested resource + action → evaluate each permission's linked policy against caller attributes → first Allow wins, otherwise Deny",
|
|
2192
2221
|
role: {
|
|
2193
|
-
description: "A named
|
|
2194
|
-
shape: {
|
|
2195
|
-
id: "UUID",
|
|
2196
|
-
name: "string (e.g., 'Data Reader', 'Compute Admin')",
|
|
2197
|
-
description: "string | null",
|
|
2198
|
-
permissions: "string[] — permission UUIDs",
|
|
2199
|
-
},
|
|
2222
|
+
description: "A named label assigned to users and service accounts. Appears in JWT claims as an attribute. Policy conditions can check roles. Names are immutable after creation.",
|
|
2223
|
+
shape: { id: "UUID", name: "string — immutable", description: "string | null" },
|
|
2200
2224
|
},
|
|
2201
2225
|
group: {
|
|
2202
|
-
description: "A named
|
|
2203
|
-
shape: {
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
},
|
|
2226
|
+
description: "A named label for organizing users and service accounts. Appears in JWT claims. Policy conditions check group membership to decide access. Groups may also optionally appear on permissions. Names are immutable after creation.",
|
|
2227
|
+
shape: { id: "UUID", name: "string — immutable", description: "string | null" },
|
|
2228
|
+
},
|
|
2229
|
+
permission: {
|
|
2230
|
+
description: "Links a resource + actions to a policy. When a request comes in, the system finds permissions matching the resource + action, then evaluates each permission's linked policy against the caller's attributes.",
|
|
2231
|
+
shape: { id: "UUID", name: "string", resourceId: "UUID (from resources)", actions: "string[]", policyId: "UUID (from policies)", priority: "number" },
|
|
2232
|
+
},
|
|
2233
|
+
policy: {
|
|
2234
|
+
description: "A reusable set of JSON-Logic rules with an effect (Allow/Deny). Policy conditions check caller attributes like groups, roles, user_id, ip_address, time of day, etc. Example: 'Allow if caller is in group engineering'.",
|
|
2235
|
+
},
|
|
2236
|
+
resource: {
|
|
2237
|
+
description: "A protected thing in the system (e.g., 'workspace::records', 'workspace::functions'). Has a category and a list of valid actions.",
|
|
2238
|
+
},
|
|
2239
|
+
granting_access: {
|
|
2240
|
+
workflow: "1. Ensure the SA is in the right group → 2. Create a policy with conditions that match (e.g., group membership check) → 3. Create a permission linking the resource + actions + policy",
|
|
2241
|
+
shortcut: "Use generate_remediation + apply_remediation to auto-generate the correct policy + permission + group assignment for a denied action",
|
|
2208
2242
|
},
|
|
2209
2243
|
permission_actions: [
|
|
2210
2244
|
"create — create new resources",
|
|
@@ -228,18 +228,18 @@ function registerServiceAccountTools(server, sdk, centraliUrl, workspaceId, ownC
|
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
230
|
}));
|
|
231
|
-
server.tool("delete_service_account", "Permanently delete a service account. This is irreversible — all tokens are invalidated immediately.", {
|
|
232
|
-
|
|
233
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
231
|
+
server.tool("delete_service_account", "Permanently delete a service account. This is irreversible — all tokens are invalidated immediately. Note: the service account must not be revoked (revoke prevents deletion).", {
|
|
232
|
+
clientId: zod_1.z.string().describe("The service account's clientId string (e.g., 'ci_abc123') — NOT the numeric ID"),
|
|
233
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ clientId }) {
|
|
234
234
|
try {
|
|
235
|
-
yield getSaClient().delete(`/${
|
|
235
|
+
yield getSaClient().delete(`/${clientId}`);
|
|
236
236
|
return {
|
|
237
|
-
content: [{ type: "text", text: `Service account '${
|
|
237
|
+
content: [{ type: "text", text: `Service account '${clientId}' deleted successfully.` }],
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
catch (error) {
|
|
241
241
|
return {
|
|
242
|
-
content: [{ type: "text", text: formatError(error, `deleting service account '${
|
|
242
|
+
content: [{ type: "text", text: formatError(error, `deleting service account '${clientId}'`) }],
|
|
243
243
|
isError: true,
|
|
244
244
|
};
|
|
245
245
|
}
|
|
@@ -553,7 +553,7 @@ function registerServiceAccountTools(server, sdk, centraliUrl, workspaceId, ownC
|
|
|
553
553
|
}
|
|
554
554
|
}));
|
|
555
555
|
// ── Role CRUD ────────────────────────────────────────────────────
|
|
556
|
-
server.tool("list_roles", "List all roles in the workspace. Roles
|
|
556
|
+
server.tool("list_roles", "List all roles in the workspace. Roles are named labels assigned to users and service accounts.", {
|
|
557
557
|
page: zod_1.z.number().optional().describe("Page number (default: 1)"),
|
|
558
558
|
pageSize: zod_1.z.number().optional().describe("Results per page (default: 20)"),
|
|
559
559
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ page, pageSize }) {
|
|
@@ -591,17 +591,14 @@ function registerServiceAccountTools(server, sdk, centraliUrl, workspaceId, ownC
|
|
|
591
591
|
};
|
|
592
592
|
}
|
|
593
593
|
}));
|
|
594
|
-
server.tool("create_role", "Create a new role
|
|
595
|
-
name: zod_1.z.string().describe("Role name (e.g., 'Data Reader', 'Compute Admin')"),
|
|
594
|
+
server.tool("create_role", "Create a new role. Roles are named labels assigned to users and service accounts. They do NOT contain permissions directly — to grant access, create a policy that targets the role as a principal. Role names are immutable after creation.", {
|
|
595
|
+
name: zod_1.z.string().describe("Role name (e.g., 'Data Reader', 'Compute Admin'). Cannot be changed after creation."),
|
|
596
596
|
description: zod_1.z.string().optional().describe("Optional description of the role's purpose"),
|
|
597
|
-
|
|
598
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({ name, description, permissions }) {
|
|
597
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ name, description }) {
|
|
599
598
|
try {
|
|
600
599
|
const input = { name };
|
|
601
600
|
if (description !== undefined)
|
|
602
601
|
input.description = description;
|
|
603
|
-
if (permissions !== undefined)
|
|
604
|
-
input.permissions = permissions;
|
|
605
602
|
const result = yield getRolesClient().post("/", input);
|
|
606
603
|
return {
|
|
607
604
|
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
@@ -614,20 +611,14 @@ function registerServiceAccountTools(server, sdk, centraliUrl, workspaceId, ownC
|
|
|
614
611
|
};
|
|
615
612
|
}
|
|
616
613
|
}));
|
|
617
|
-
server.tool("update_role", "Update a role's
|
|
614
|
+
server.tool("update_role", "Update a role's description. Role names are immutable. Roles are named labels assigned to users/service accounts — they do NOT contain permissions directly. To grant access, create a policy that references the role.", {
|
|
618
615
|
roleId: zod_1.z.string().describe("The role ID (UUID) to update"),
|
|
619
|
-
name: zod_1.z.string().optional().describe("Updated name"),
|
|
620
616
|
description: zod_1.z.string().optional().describe("Updated description"),
|
|
621
|
-
|
|
622
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({ roleId, name, description, permissions }) {
|
|
617
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ roleId, description }) {
|
|
623
618
|
try {
|
|
624
619
|
const input = {};
|
|
625
|
-
if (name !== undefined)
|
|
626
|
-
input.name = name;
|
|
627
620
|
if (description !== undefined)
|
|
628
621
|
input.description = description;
|
|
629
|
-
if (permissions !== undefined)
|
|
630
|
-
input.permissions = permissions;
|
|
631
622
|
const result = yield getRolesClient().put(`/${roleId}`, input);
|
|
632
623
|
return {
|
|
633
624
|
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
@@ -640,7 +631,7 @@ function registerServiceAccountTools(server, sdk, centraliUrl, workspaceId, ownC
|
|
|
640
631
|
};
|
|
641
632
|
}
|
|
642
633
|
}));
|
|
643
|
-
server.tool("delete_role", "Delete a role. Service accounts
|
|
634
|
+
server.tool("delete_role", "Delete a role. Service accounts assigned to this role will lose the label.", {
|
|
644
635
|
roleId: zod_1.z.string().describe("The role ID (UUID) to delete"),
|
|
645
636
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ roleId }) {
|
|
646
637
|
try {
|
|
@@ -715,15 +706,12 @@ function registerServiceAccountTools(server, sdk, centraliUrl, workspaceId, ownC
|
|
|
715
706
|
};
|
|
716
707
|
}
|
|
717
708
|
}));
|
|
718
|
-
server.tool("update_group", "Update a group's
|
|
709
|
+
server.tool("update_group", "Update a group's description. Group names are immutable and cannot be changed after creation.", {
|
|
719
710
|
groupId: zod_1.z.string().describe("The group ID (UUID) to update"),
|
|
720
|
-
name: zod_1.z.string().optional().describe("Updated name"),
|
|
721
711
|
description: zod_1.z.string().optional().describe("Updated description"),
|
|
722
|
-
}, (_a) => __awaiter(this, [_a], void 0, function* ({ groupId,
|
|
712
|
+
}, (_a) => __awaiter(this, [_a], void 0, function* ({ groupId, description }) {
|
|
723
713
|
try {
|
|
724
714
|
const input = {};
|
|
725
|
-
if (name !== undefined)
|
|
726
|
-
input.name = name;
|
|
727
715
|
if (description !== undefined)
|
|
728
716
|
input.description = description;
|
|
729
717
|
const result = yield getGroupsClient().put(`/${groupId}`, input);
|
|
@@ -940,7 +928,7 @@ function registerServiceAccountTools(server, sdk, centraliUrl, workspaceId, ownC
|
|
|
940
928
|
}
|
|
941
929
|
}));
|
|
942
930
|
server.tool("create_permission", "Create a new permission definition. Permissions bind actions to a resource within a policy. Required fields: name, resourceId (UUID from list_resources), actions (string array), policyId (UUID from list_policies or create_policy).", {
|
|
943
|
-
permission: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).describe("Required: { name: string, resourceId: UUID, actions: string[], policyId: UUID }. Optional: description,
|
|
931
|
+
permission: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).describe("Required: { name: string, resourceId: UUID, actions: string[], policyId: UUID }. Optional: description, priority (number)."),
|
|
944
932
|
}, (_a) => __awaiter(this, [_a], void 0, function* ({ permission }) {
|
|
945
933
|
try {
|
|
946
934
|
const result = yield getPermissionsClient().post("/", permission);
|
package/package.json
CHANGED
package/src/tools/describe.ts
CHANGED
|
@@ -346,18 +346,46 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
346
346
|
},
|
|
347
347
|
render_url: {
|
|
348
348
|
method: "client.getFileRenderUrl(renderId, options?)",
|
|
349
|
-
|
|
350
|
-
|
|
349
|
+
important: "uploadFile() returns a renderId (string), NOT a URL. You MUST call getFileRenderUrl(renderId) to construct the actual URL for display.",
|
|
350
|
+
url_pattern: "https://api.{domain}/storage/ws/{workspaceId}/api/v1/render/{renderId}",
|
|
351
|
+
transforms: "Optional image transformations: width, height, fit ('cover'|'contain'|'fill'), format ('webp'|'png'|'jpeg'), quality (1-100).",
|
|
352
|
+
public_vs_private: {
|
|
353
|
+
public: "If uploaded with isPublic=true, the render URL works without any auth — use directly in <img> tags.",
|
|
354
|
+
private: "If uploaded with isPublic=false (default), the render URL requires authentication. Pass a Bearer token (service account) or x-api-key (publishable key) in the request headers. For browser rendering of private images, use a server-side proxy route or a publishable key.",
|
|
355
|
+
},
|
|
356
|
+
example: [
|
|
357
|
+
"// Upload returns renderId, NOT a URL",
|
|
358
|
+
"const { data: renderId } = await centrali.uploadFile(file, '/root/shared/logos', true);",
|
|
359
|
+
"",
|
|
360
|
+
"// Build the render URL from the renderId",
|
|
361
|
+
"const url = centrali.getFileRenderUrl(renderId);",
|
|
362
|
+
"// => 'https://api.centrali.io/storage/ws/my-workspace/api/v1/render/abc123'",
|
|
363
|
+
"",
|
|
364
|
+
"// With image transforms (thumbnail)",
|
|
365
|
+
"const thumbUrl = centrali.getFileRenderUrl(renderId, { width: 200, height: 200, fit: 'cover', format: 'webp' });",
|
|
366
|
+
"",
|
|
367
|
+
"// For private images in Next.js — proxy through an API route",
|
|
368
|
+
"// app/api/image/[renderId]/route.ts",
|
|
369
|
+
"export async function GET(req, { params }) {",
|
|
370
|
+
" const centrali = new CentraliSDK({ baseUrl, workspaceId, clientId, clientSecret });",
|
|
371
|
+
" const url = centrali.getFileRenderUrl(params.renderId);",
|
|
372
|
+
" const resp = await fetch(url, {",
|
|
373
|
+
" headers: { Authorization: `Bearer ${await centrali.getToken()}` }",
|
|
374
|
+
" });",
|
|
375
|
+
" return new Response(resp.body, { headers: { 'Content-Type': resp.headers.get('Content-Type') } });",
|
|
376
|
+
"}",
|
|
377
|
+
].join("\n"),
|
|
351
378
|
},
|
|
352
379
|
download_url: {
|
|
353
380
|
method: "client.getFileDownloadUrl(renderId)",
|
|
354
|
-
note: "Returns a URL that triggers a file download.",
|
|
381
|
+
note: "Returns a URL that triggers a file download. Same auth rules as render URLs.",
|
|
355
382
|
},
|
|
356
383
|
tips: [
|
|
357
384
|
"/root/shared always exists — upload there directly or create subfolders like /root/shared/logos",
|
|
358
385
|
"Set isPublic=true for files that need to be accessible without auth (logos, avatars, public images)",
|
|
359
|
-
"Store the renderId
|
|
386
|
+
"Store the renderId on a record field — then use getFileRenderUrl() to build the URL when displaying",
|
|
360
387
|
"Use image transformations for thumbnails instead of uploading multiple sizes",
|
|
388
|
+
"For private images in browser apps: either use a server-side proxy route or a publishable key with storage scope",
|
|
361
389
|
],
|
|
362
390
|
},
|
|
363
391
|
realtime: {
|
|
@@ -2485,23 +2513,29 @@ export function registerDescribeTools(server: McpServer) {
|
|
|
2485
2513
|
createdAt: "string — ISO 8601 timestamp",
|
|
2486
2514
|
},
|
|
2487
2515
|
permission_model: {
|
|
2488
|
-
description: "
|
|
2516
|
+
description: "Access control is attribute-based. The caller's groups, roles, and other attributes (from JWT claims) are evaluated against policy conditions using JSON-Logic.",
|
|
2517
|
+
evaluation_flow: "Request → extract groups/roles from JWT → find permissions for the requested resource + action → evaluate each permission's linked policy against caller attributes → first Allow wins, otherwise Deny",
|
|
2489
2518
|
role: {
|
|
2490
|
-
description: "A named
|
|
2491
|
-
shape: {
|
|
2492
|
-
id: "UUID",
|
|
2493
|
-
name: "string (e.g., 'Data Reader', 'Compute Admin')",
|
|
2494
|
-
description: "string | null",
|
|
2495
|
-
permissions: "string[] — permission UUIDs",
|
|
2496
|
-
},
|
|
2519
|
+
description: "A named label assigned to users and service accounts. Appears in JWT claims as an attribute. Policy conditions can check roles. Names are immutable after creation.",
|
|
2520
|
+
shape: { id: "UUID", name: "string — immutable", description: "string | null" },
|
|
2497
2521
|
},
|
|
2498
2522
|
group: {
|
|
2499
|
-
description: "A named
|
|
2500
|
-
shape: {
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
},
|
|
2523
|
+
description: "A named label for organizing users and service accounts. Appears in JWT claims. Policy conditions check group membership to decide access. Groups may also optionally appear on permissions. Names are immutable after creation.",
|
|
2524
|
+
shape: { id: "UUID", name: "string — immutable", description: "string | null" },
|
|
2525
|
+
},
|
|
2526
|
+
permission: {
|
|
2527
|
+
description: "Links a resource + actions to a policy. When a request comes in, the system finds permissions matching the resource + action, then evaluates each permission's linked policy against the caller's attributes.",
|
|
2528
|
+
shape: { id: "UUID", name: "string", resourceId: "UUID (from resources)", actions: "string[]", policyId: "UUID (from policies)", priority: "number" },
|
|
2529
|
+
},
|
|
2530
|
+
policy: {
|
|
2531
|
+
description: "A reusable set of JSON-Logic rules with an effect (Allow/Deny). Policy conditions check caller attributes like groups, roles, user_id, ip_address, time of day, etc. Example: 'Allow if caller is in group engineering'.",
|
|
2532
|
+
},
|
|
2533
|
+
resource: {
|
|
2534
|
+
description: "A protected thing in the system (e.g., 'workspace::records', 'workspace::functions'). Has a category and a list of valid actions.",
|
|
2535
|
+
},
|
|
2536
|
+
granting_access: {
|
|
2537
|
+
workflow: "1. Ensure the SA is in the right group → 2. Create a policy with conditions that match (e.g., group membership check) → 3. Create a permission linking the resource + actions + policy",
|
|
2538
|
+
shortcut: "Use generate_remediation + apply_remediation to auto-generate the correct policy + permission + group assignment for a denied action",
|
|
2505
2539
|
},
|
|
2506
2540
|
permission_actions: [
|
|
2507
2541
|
"create — create new resources",
|
|
@@ -261,19 +261,19 @@ export function registerServiceAccountTools(server: McpServer, sdk: CentraliSDK,
|
|
|
261
261
|
|
|
262
262
|
server.tool(
|
|
263
263
|
"delete_service_account",
|
|
264
|
-
"Permanently delete a service account. This is irreversible — all tokens are invalidated immediately.",
|
|
264
|
+
"Permanently delete a service account. This is irreversible — all tokens are invalidated immediately. Note: the service account must not be revoked (revoke prevents deletion).",
|
|
265
265
|
{
|
|
266
|
-
|
|
266
|
+
clientId: z.string().describe("The service account's clientId string (e.g., 'ci_abc123') — NOT the numeric ID"),
|
|
267
267
|
},
|
|
268
|
-
async ({
|
|
268
|
+
async ({ clientId }) => {
|
|
269
269
|
try {
|
|
270
|
-
await getSaClient().delete(`/${
|
|
270
|
+
await getSaClient().delete(`/${clientId}`);
|
|
271
271
|
return {
|
|
272
|
-
content: [{ type: "text", text: `Service account '${
|
|
272
|
+
content: [{ type: "text", text: `Service account '${clientId}' deleted successfully.` }],
|
|
273
273
|
};
|
|
274
274
|
} catch (error: unknown) {
|
|
275
275
|
return {
|
|
276
|
-
content: [{ type: "text", text: formatError(error, `deleting service account '${
|
|
276
|
+
content: [{ type: "text", text: formatError(error, `deleting service account '${clientId}'`) }],
|
|
277
277
|
isError: true,
|
|
278
278
|
};
|
|
279
279
|
}
|
|
@@ -682,7 +682,7 @@ export function registerServiceAccountTools(server: McpServer, sdk: CentraliSDK,
|
|
|
682
682
|
|
|
683
683
|
server.tool(
|
|
684
684
|
"list_roles",
|
|
685
|
-
"List all roles in the workspace. Roles
|
|
685
|
+
"List all roles in the workspace. Roles are named labels assigned to users and service accounts.",
|
|
686
686
|
{
|
|
687
687
|
page: z.number().optional().describe("Page number (default: 1)"),
|
|
688
688
|
pageSize: z.number().optional().describe("Results per page (default: 20)"),
|
|
@@ -728,17 +728,15 @@ export function registerServiceAccountTools(server: McpServer, sdk: CentraliSDK,
|
|
|
728
728
|
|
|
729
729
|
server.tool(
|
|
730
730
|
"create_role",
|
|
731
|
-
"Create a new role
|
|
731
|
+
"Create a new role. Roles are named labels assigned to users and service accounts. They do NOT contain permissions directly — to grant access, create a policy that targets the role as a principal. Role names are immutable after creation.",
|
|
732
732
|
{
|
|
733
|
-
name: z.string().describe("Role name (e.g., 'Data Reader', 'Compute Admin')"),
|
|
733
|
+
name: z.string().describe("Role name (e.g., 'Data Reader', 'Compute Admin'). Cannot be changed after creation."),
|
|
734
734
|
description: z.string().optional().describe("Optional description of the role's purpose"),
|
|
735
|
-
permissions: z.array(z.string()).optional().describe("Array of permission UUIDs to include in this role"),
|
|
736
735
|
},
|
|
737
|
-
async ({ name, description
|
|
736
|
+
async ({ name, description }) => {
|
|
738
737
|
try {
|
|
739
738
|
const input: Record<string, any> = { name };
|
|
740
739
|
if (description !== undefined) input.description = description;
|
|
741
|
-
if (permissions !== undefined) input.permissions = permissions;
|
|
742
740
|
const result = await getRolesClient().post("/", input);
|
|
743
741
|
return {
|
|
744
742
|
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
@@ -754,19 +752,15 @@ export function registerServiceAccountTools(server: McpServer, sdk: CentraliSDK,
|
|
|
754
752
|
|
|
755
753
|
server.tool(
|
|
756
754
|
"update_role",
|
|
757
|
-
"Update a role's
|
|
755
|
+
"Update a role's description. Role names are immutable. Roles are named labels assigned to users/service accounts — they do NOT contain permissions directly. To grant access, create a policy that references the role.",
|
|
758
756
|
{
|
|
759
757
|
roleId: z.string().describe("The role ID (UUID) to update"),
|
|
760
|
-
name: z.string().optional().describe("Updated name"),
|
|
761
758
|
description: z.string().optional().describe("Updated description"),
|
|
762
|
-
permissions: z.array(z.string()).optional().describe("Updated permission UUIDs (replaces all existing)"),
|
|
763
759
|
},
|
|
764
|
-
async ({ roleId,
|
|
760
|
+
async ({ roleId, description }) => {
|
|
765
761
|
try {
|
|
766
762
|
const input: Record<string, any> = {};
|
|
767
|
-
if (name !== undefined) input.name = name;
|
|
768
763
|
if (description !== undefined) input.description = description;
|
|
769
|
-
if (permissions !== undefined) input.permissions = permissions;
|
|
770
764
|
const result = await getRolesClient().put(`/${roleId}`, input);
|
|
771
765
|
return {
|
|
772
766
|
content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }],
|
|
@@ -782,7 +776,7 @@ export function registerServiceAccountTools(server: McpServer, sdk: CentraliSDK,
|
|
|
782
776
|
|
|
783
777
|
server.tool(
|
|
784
778
|
"delete_role",
|
|
785
|
-
"Delete a role. Service accounts
|
|
779
|
+
"Delete a role. Service accounts assigned to this role will lose the label.",
|
|
786
780
|
{
|
|
787
781
|
roleId: z.string().describe("The role ID (UUID) to delete"),
|
|
788
782
|
},
|
|
@@ -875,16 +869,14 @@ export function registerServiceAccountTools(server: McpServer, sdk: CentraliSDK,
|
|
|
875
869
|
|
|
876
870
|
server.tool(
|
|
877
871
|
"update_group",
|
|
878
|
-
"Update a group's
|
|
872
|
+
"Update a group's description. Group names are immutable and cannot be changed after creation.",
|
|
879
873
|
{
|
|
880
874
|
groupId: z.string().describe("The group ID (UUID) to update"),
|
|
881
|
-
name: z.string().optional().describe("Updated name"),
|
|
882
875
|
description: z.string().optional().describe("Updated description"),
|
|
883
876
|
},
|
|
884
|
-
async ({ groupId,
|
|
877
|
+
async ({ groupId, description }) => {
|
|
885
878
|
try {
|
|
886
879
|
const input: Record<string, any> = {};
|
|
887
|
-
if (name !== undefined) input.name = name;
|
|
888
880
|
if (description !== undefined) input.description = description;
|
|
889
881
|
const result = await getGroupsClient().put(`/${groupId}`, input);
|
|
890
882
|
return {
|
|
@@ -1168,7 +1160,7 @@ export function registerServiceAccountTools(server: McpServer, sdk: CentraliSDK,
|
|
|
1168
1160
|
"create_permission",
|
|
1169
1161
|
"Create a new permission definition. Permissions bind actions to a resource within a policy. Required fields: name, resourceId (UUID from list_resources), actions (string array), policyId (UUID from list_policies or create_policy).",
|
|
1170
1162
|
{
|
|
1171
|
-
permission: z.record(z.string(), z.any()).describe("Required: { name: string, resourceId: UUID, actions: string[], policyId: UUID }. Optional: description,
|
|
1163
|
+
permission: z.record(z.string(), z.any()).describe("Required: { name: string, resourceId: UUID, actions: string[], policyId: UUID }. Optional: description, priority (number)."),
|
|
1172
1164
|
},
|
|
1173
1165
|
async ({ permission }) => {
|
|
1174
1166
|
try {
|