post_roles_by_id_permissions

Changes data · you confirm first

Calls POST /api/roles/{id}/permissions as the signed-in person · REST reference

Grant permissions to a role

Requires execute:AddPermissionToRoleCommand; platform staff bypass that gate. The caller's own token is forwarded, and the write is the DAL's UpdateRoleCommand: it requires execute:UpdateRoleCommand in the role's own organization and refuses — 403 — a system role, a role of the platform organization, or a role that belongs to no organization to anyone without platform authority. For such a caller, a role in an organization where they do not hold admin.roles.view is a 404. Permission names are stored only on a non-system role that belongs to no organization. For an organization's role, whose grants are :OrgPermission edges, the DAL refuses the write with 400; for platform staff on a system role, with 409. ADDITIVE AND IDEMPOTENT: permissions already held are filtered out, and if every submitted permission is already present the model skips the save entirely — the response is the same 200 either way, so it does not distinguish "granted" from "no change". Each new name must be execute: or query: followed by a PascalCase name ending in Command or Query; the DAL refuses any other string (400). Names are not checked against a catalogue. The response carries data: null — no role and no resulting permission set is returned.

Arguments

ArgumentTypeDescription
idpathstring (uuid)required
bodyJSONobjectrequiredThe request body, as a JSON object. Its fields are listed below.

Fields of body

FieldTypeDescription
permissionsarray of stringrequiredRequired and must be an array of strings. An empty array is accepted and is a no-op.

Example call

A client sends this over the MCP session; Claude and other agents build it for you. The values are placeholders — the shape is real.

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "post_roles_by_id_permissions",
    "arguments": {
      "id": "11111111-1111-4111-8111-111111111111",
      "body": {
        "permissions": [
          "string"
        ]
      }
    }
  }
}
Confirmation
The client shows the person the exact request and waits. Nothing runs until they tick Make this change. See Reading and changing.

Result

On success (HTTP 200) the tool returns one text item holding the API's JSON, pretty-printed.

Acknowledgement. data is explicitly null; nothing is returned.

json
{
  "data": {},
  "message": "Permissions added to role successfully",
  "success": true
}

Fields

FieldTypeDescription
dataany | nullAlways JSON null — the key is present because the controller passes an explicit null rather than omitting the argument.
message"Permissions added to role successfully"
successtrue

When it fails

A failed call returns isError: true with the API's message as text. A change the person declines, or lets time out, also comes back this way and nothing changes.

400

id is not a UUID, permissions is missing or not an array, or an element is not a string, with an ARRAY-shaped details; or the DAL refused the write — a permission name not of the execute:/query: shape, or an organization's role — carrying the DAL's sentence.

403

Authenticated, but the caller holds none of the required permissions.

404

No such role — Role not found with code: ROLE_NOT_FOUND — or a DAL NOT_FOUND refusal, which carries the DAL sentence and no code.

409

The resource is not in a state that allows this operation.

What the model is shown

This is the tool exactly as the server lists it in tools/list. The description starts with the REST call it makes.

json
{
  "name": "post_roles_by_id_permissions",
  "description": "POST /api/roles/{id}/permissions — Requires `execute:AddPermissionToRoleCommand`; platform staff bypass that gate. The caller's own token is forwarded, and the write is the DAL's `UpdateRoleCommand`: it requires `execute:UpdateRoleCommand` in the role's own organization and refuses — 403 — a system role, a role of the platform organization, or a role that belongs to no organization to anyone without platform authority. For such a caller, a role in an organization where they do not hold `admin.roles.view` is a 404. Permission names are stored only on a non-system role that belongs to no organization. For an organization's role, whose grants are `:OrgPermission` edges, the DAL refuses the write with 400; for platform staff on a system role, with 409. ADDITIVE AND IDEMPOTENT: permissions already held are filtered out, and if every submitted permission is already present the model skips the save entirely — the response is the same 200 either way, so it does not distinguish \"granted\" from \"no change\". Each new name must be `execute:` or `query:` followed by a PascalCase name ending in `Command` or `Query`; the DAL refuses any other string (400). Names are not checked against a catalogue. The response carries `data: null` — no role and no resulting permission set is returned.\n\nThis changes data. The person is asked to confirm it before it runs, and nothing happens if they decline.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "id": {
        "type": "string"
      },
      "body": {
        "type": "string",
        "description": "The JSON request body: { permissions*: array of string (Required and must be an array of strings.) }"
      }
    },
    "required": [
      "id",
      "body"
    ]
  },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": false,
    "openWorldHint": false
  }
}