Agent Skills / maison-access

Install this skill alone, or read it below. Raw file: SKILL.md · references/tools.md

bash
npx skills add https://docs.maison-labs.com --skill maison-access

Maison — Roles, policies and groups

Shape permissions on Maison: roles, policies and user groups, each available to one hotel or to all clients (roles and policies may be Maison only). A policy is a list of rules (feature + level) and is never applied to a client directly — a role applies each of its policies at all clients or at named clients (Applies to). Also the feature catalogue and effective-access views. Use when designing who may do what, troubleshooting a 403, or granting a team a bundle of permissions.

Part of the Maison public API. Read the maison-public-api skill first for authentication, discovery and transport details (https://docs.maison-labs.com/skills/maison-public-api/SKILL.md).

When to use

  • Set what a policy allows from a description ("can view conversations and analytics but change nothing"): set_policy_rules (Maison policy) or set_client_policy_rules (hotel policy) save the full rule list in one call.
  • Create a reusable permission bundle for a hotel — create_client_policy with statements, attach it to a role with attach_client_role_policy.
  • Explain a 403 — get_client_user_access (users skill) or get_client_role / get_client_policy effective access.
  • Roles and groups available to all clients — create_role (clientId null), create_group, update_group_roles; apply a shared policy at chosen hotels with attach_role_policy and change it with set_role_policy_applies_to.
  • Bring a retired role back — restore_role. Page a role's holders past get_role's first 50 — list_role_users, list_role_groups.
  • Maison policies end to end — create_policy, set_policy_rules, attach_role_policy; update_policy renames or re-describes; delete_policy once no role applies it.
  • Rename a hotel group or drop it — update_client_group, delete_client_group.

Before you start

  1. Send x-api-key: <key> on every call; the key only reaches /api/v1.
  2. GET /api/v1/tools (or MCP tools/list) returns the tools THIS key may run — if a tool below is missing there, you lack a grant; do not retry it.
  3. Client-scoped tools take clientId (a hotel id). whoami lists the ids you can act on.

Tools

ToolWhat it doesRESTNeeds
add_client_policy_statementSet one rule (a feature and a level — a rule carries no client; where the policy applies is each role's Applies to) in a hotel-owned policy.POST /api/v1/client/{clientId}/policy/{policyId}/statementpolicies WRITE (client)
add_client_role_ruleSet one rule (a feature and a level) in a hotel role's own policy; the role applies it at this hotel.POST /api/v1/client/{clientId}/role/{roleId}/rulemaison-roles WRITE (client), policies WRITE (client)
add_role_policySet one rule (a feature and a level — a rule has no client; where it applies is the role's Applies to) in the role's own policy.POST /api/v1/role/{roleId}/policymaison-roles WRITE (global), policies WRITE (global)
assign_roleGive a user this role at one client (clientId) or at all clients (null).POST /api/v1/role/{roleId}/usermaison-users WRITE (client)
assign_role_to_groupGive a group this role at one client (clientId) or at all clients (null) so its members inherit it.POST /api/v1/role/{roleId}/groupmaison-users WRITE (client)
attach_client_role_policyAttach an existing policy to a hotel role; the role applies it at this hotel.POST /api/v1/client/{clientId}/role/{roleId}/policymaison-roles WRITE (client)
attach_role_policyAttach an existing policy to a role and say where the role applies it (appliesTo: null = all clients, or client ids).POST /api/v1/role/{roleId}/policy/{policyId}/attachmaison-roles WRITE (any)
create_client_groupCreate a new access group owned by a hotel and optionally seed it with members and a role.POST /api/v1/client/{clientId}/groupgroups WRITE (client); composite of update_client_group_members, link_client_group_role
create_client_policyCreate a policy available to one hotel and optionally seed it with rules (each a feature and a level).POST /api/v1/client/{clientId}/policypolicies WRITE (client); composite of add_client_policy_statement
create_client_roleCreate a new role owned by a hotel and optionally seed it with rules (a feature and a level each; seeding also needs the policies grant at the hotel, checked before the role exists).POST /api/v1/client/{clientId}/rolemaison-roles WRITE (client); composite of add_client_role_rule
create_groupCreate a user group available to all clients (needs the global groups grant) — Maison only with visibilityMaisonOnly — and optionally seed it with members (update_group_members) and role assignments (update_group_roles, which also need the users grant where each role is given).POST /api/v1/groupgroups WRITE (global); composite of update_group_members, update_group_roles
create_policyCreate a policy available to all clients (visibilityMaisonOnly makes it Maison only), with no rules yet: fill them with set_policy_rules, then apply it with attach_role_policy.POST /api/v1/policypolicies WRITE (global)
create_roleCreate a role available to one client (clientId) or to all clients (clientId null; visibilityMaisonOnly makes it Maison only), optionally seeded with rules on its own policy.POST /api/v1/rolemaison-roles WRITE (client); composite of add_role_policy
delete_client_groupDelete (soft) a group the hotel owns, with its role links.DELETE /api/v1/client/{clientId}/group/{groupId}groups WRITE (client); destructive
delete_client_policySoft-delete a hotel-owned policy.DELETE /api/v1/client/{clientId}/policy/{policyId}policies WRITE (client); destructive
delete_groupSoft-delete a group and cascade to members and role links; needs the groups grant where the group is available.DELETE /api/v1/group/{groupId}groups WRITE (any); destructive
delete_policyDelete (soft) a policy available to all clients.DELETE /api/v1/policy/{policyId}policies WRITE (global); destructive
detach_client_role_policyStop a hotel role applying a policy.DELETE /api/v1/client/{clientId}/role/{roleId}/policy/{policyId}maison-roles WRITE (client); destructive
detach_role_policyStop a role applying a policy anywhere (every client it applied at).POST /api/v1/role/{roleId}/policy/{policyId}/detachmaison-roles WRITE (any); destructive
disable_client_roleSoft-disable a hotel-owned role.POST /api/v1/client/{clientId}/role/{roleId}/disablemaison-roles WRITE (client); destructive
disable_roleRetire a role (sets disabled_date); needs the roles grant where the role is available.POST /api/v1/role/{roleId}/disablemaison-roles WRITE (any); destructive
get_client_groupFetch a hotel group's members, candidate users, role assignments, and effective access summary.GET /api/v1/client/{clientId}/group/{groupId}groups READ (client), maison-users READ (client); read-only
get_client_policyFetch a hotel policy's rules (feature + level) and its effective access summary.GET /api/v1/client/{clientId}/policy/{policyId}policies READ (client); read-only
get_client_roleFetch a hotel role's policies and its effective access summary.GET /api/v1/client/{clientId}/role/{roleId}maison-roles READ (client); read-only
get_groupFull detail of a group you can see, including members and role assignments.GET /api/v1/group/{groupId}groups READ (any); read-only
get_policyFetch one policy you can see with its rules (feature + level) and the roles that apply it.GET /api/v1/policy/{policyId}policies READ (any); read-only
get_roleFetch a role's detail — its attached policies, each with applies_to (all: true, or the named clients where the role applies it) and rule count — its first page of direct users, and its first page of groups.GET /api/v1/role/{roleId}maison-roles READ (any); read-only
link_client_group_roleCreate a user_group_role_link so a hotel-owned group inherits a role at this hotel.POST /api/v1/client/{clientId}/usergroupgroups WRITE (client), maison-users WRITE (client)
list_client_features_catalogueFeatures available for hotel-scoped roles and policies at a specific hotel, with their display labels.GET /api/v1/client/{clientId}/featurescataloguepolicies READ (client); read-only
list_client_groupsThe access groups available at a hotel: its own and those available to all clients — never another hotel's.GET /api/v1/client/{clientId}/groupsgroups READ (client); read-only
list_client_policiesThe policies available at a hotel: its own and those available to all clients (never Maison-only ones, never another hotel's).GET /api/v1/client/{clientId}/policiespolicies READ (client); read-only
list_client_rolesRoles that can be assigned to users or groups at a specific hotel (excludes Maison-only and disabled roles).GET /api/v1/client/{clientId}/rolesmaison-roles READ (client); read-only
list_featuresRead-only catalogue of all Maison features: id, key (name), display label, purpose (what it does and where), description and maison_only.GET /api/v1/featuresmaison-roles READ (global); read-only
list_groupsPage through the user groups you can see, with optional search and sort (name, member count or creation): groups available to all clients and those of each client you read — never another client's; Maison-only groups (visibility_maison_only) only with the global groups read grant.GET /api/v1/groupsgroups READ (any); read-only
list_policiesPage through the policies you can see (search by name or description), with rule and role counts: policies available to all clients, Maison-only ones (with the global policies grant) and those of each client you read — never another client's.GET /api/v1/policiespolicies READ (any); read-only
list_role_groupsPage through the groups given a role (their members inherit it), each with the client the link is at.GET /api/v1/role/{roleId}/groupsmaison-roles READ (any); read-only
list_role_usersPage through the users holding a role directly, each with the client the link is at (null = all clients).GET /api/v1/role/{roleId}/usersmaison-roles READ (any); read-only
list_rolesPage through the roles you can see, with optional search, sort and filters (scope, ownerClientId, includeRetired): roles available to all clients, Maison-only roles (with the global roles grant) and the roles of each client you read — never another client's.GET /api/v1/rolesmaison-roles READ (any); read-only
remove_client_policy_statementSoft-delete one statement from a hotel-owned policy.DELETE /api/v1/client/{clientId}/policy/{policyId}/statement/{stmtId}policies WRITE (client); destructive
remove_client_role_ruleRemove one rule (the id add_client_role_rule returned, or from get_client_role) from a hotel role's own policy.DELETE /api/v1/client/{clientId}/role/{roleId}/rule/{ruleId}maison-roles WRITE (client), policies WRITE (client); destructive
remove_role_policySoft-delete one rule from the role's own policy; needs the roles and policies grants where the role is available.DELETE /api/v1/role/{roleId}/policy/{policyId}maison-roles WRITE (any), policies WRITE (any); destructive
restore_roleBring a retired role back (clears disabled_date) so it can be assigned again; its policies and links are as they were.POST /api/v1/role/{roleId}/restoremaison-roles WRITE (any)
set_client_policy_rulesSave a hotel-owned policy's complete rule list in one call (the client Policies tab's rules table): each rule is a feature, or every feature, at READ, WRITE, EXECUTE or DENY.PUT /api/v1/client/{clientId}/policy/{policyId}/rulespolicies WRITE (client)
set_policy_rulesSave a Maison policy's complete rule list in one call (the policy page's rules table): each rule is a feature, or every feature, at READ, WRITE, EXECUTE or DENY.PUT /api/v1/policy/{policyId}/rulespolicies WRITE (global)
set_role_policy_applies_toReplace where a role applies one of its policies (appliesTo: null = all clients, or client ids) in one call: clients outside the new set stop, missing ones start.PUT /api/v1/role/{roleId}/policy/{policyId}/appliestomaison-roles WRITE (any)
unassign_roleTake a role away from a user: every scope by default, or only one with linkScope (global for the link at all clients, or a client id).DELETE /api/v1/role/{roleId}/user/{userId}maison-users WRITE (any); destructive
unlink_client_groupSoft-delete all role links for a hotel-owned group at this hotel.DELETE /api/v1/client/{clientId}/usergroup/{groupId}groups WRITE (client); destructive
update_client_groupRename or re-describe a group the hotel owns (description null clears it).PUT /api/v1/client/{clientId}/group/{groupId}groups WRITE (client)
update_client_group_membersAdd or remove members in a hotel-owned access group.PUT /api/v1/client/{clientId}/group/{groupId}/membersgroups WRITE (client)
update_client_policyPatch a hotel-owned policy's name or description.PUT /api/v1/client/{clientId}/policy/{policyId}policies WRITE (client)
update_client_rolePatch a hotel-owned role's name or description.PUT /api/v1/client/{clientId}/role/{roleId}maison-roles WRITE (client)
update_groupPatch a group's name, description or Maison-only flag (visibilityMaisonOnly, which needs the global groups grant and a group available to all clients — 400 on a hotel's group); needs the groups grant where the group is available.PUT /api/v1/group/{groupId}groups WRITE (any)
update_group_membersAdd or remove members of a group; needs the groups grant where the group is available (and the global roles grant for a group carrying a Maison-only role).PUT /api/v1/group/{groupId}/membersgroups WRITE (any)
update_group_rolesAdd or remove role assignments of a group (each at a client, or at all clients with clientId null); needs the groups grant where the group is available, and the users grant where each role is given.PUT /api/v1/group/{groupId}/rolesgroups WRITE (any), maison-users WRITE (any)
update_policyRename or re-describe a policy available to all clients, or set / clear its Maison-only flag.PUT /api/v1/policy/{policyId}policies WRITE (global)
update_rolePatch a role's name, description or Maison-only flag (visibilityMaisonOnly, which needs the global roles grant).PUT /api/v1/role/{roleId}maison-roles WRITE (any)

Full input fields for every tool: references/tools.md.

Recommended workflows

Set a policy's rules from what the person describes

  1. get_policy (or get_client_policy) for the current rules; list_features (or list_client_features_catalogue) for each feature's id, label and purpose.
  2. Map the words to levels: view → READ, change or manage → WRITE, run, send or act → EXECUTE, never or block → DENY. Name the specific features; the every-feature rule (featureId null) only when they mean everything.
  3. Ambiguous (which features, which level)? Show the rules as a short Feature / Level list and ask before saving.
  4. set_policy_rules (or set_client_policy_rules) with the FULL list: the current rules you keep plus the changes, one rule per feature. A rule left out is removed.

Grant a hotel team a defined set of features

  1. list_client_features_catalogue to see which features the hotel's plan allows for roles.
  2. create_client_policy with statements: [{ featureId, permission }] (or create_client_role with inline rules; add or remove one later with add_client_role_rule / remove_client_role_rule); a rule names no client.
  3. create_client_group with memberIds and roleId, or assign per user with set_client_user_roles.
  4. Verify with get_client_group / get_client_role effective access.

Tips

  • Levels: READ < WRITE < EXECUTE; DENY wins over any grant at the same scope. A policy holds at most one rule per feature (and at most one every-feature rule), at exactly one of READ, WRITE, EXECUTE or DENY; a higher level includes the lower ones (WRITE already includes READ).
  • Nobody can grant a feature level they do not hold themselves on that hotel (no-escalation rule) — a 403 while adding a statement usually means that.
  • A rule granting a Maison-only feature (flagged maison_only in list_features), an every-feature rule, and attaching a policy that grants a Maison-only feature all need allow-maison-only WRITE held globally and by name — an ALL wildcard never counts (403 names allow-maison-only). A DENY rule needs nothing extra.

Pitfalls

  • The rules tools replace the whole list, and one rule beyond your own access refuses the whole save (403 names the feature and level; nothing changed). One feature at two levels in the list is a 400 (nothing saved).
  • add_role_policy, add_client_role_rule and add_client_policy_statement set the feature's one rule: a different level replaces the current rule, the same level changes nothing.
  • Each kind has one grant, at the hotel for a row available to that hotel and GLOBAL for a row available to all clients (or Maison only): roles → maison-roles (also attaching policies to a role), groups → groups, policies → policies (and their rules), giving or revoking a role or a membership → maison-users. clients WRITE grants nothing in access management. Read with the same feature at READ.
  • A role's own inline rules (add_role_policy, create_role with policies) need GLOBAL policies WRITE; a hotel's rules (set_client_policy_rules, add_client_policy_statement) need policies WRITE on the hotel.
  • A policy available to one hotel applies only there; a role available to one hotel applies its policies there only (appliesTo null). A rule input naming a client, region, department or tag is a validation error — scope lives on the role, never on a rule.
  • Disabling a role is soft (disable_role); the role's users lose the permissions immediately.
  • delete_client_group is 409 while the group has members (pending invitations included) and delete_policy is 409 while a role applies it: empty or detach first.
  • unassign_role without linkScope removes the role at every scope; pass linkScope: "global" or a client id to remove one link.

Calling conventions

Same tool, three transports — pick whichever your runtime already speaks. Always send x-api-key.

bash
# REST
curl -X POST "https://maison-labs.com/api/v1/client/<clientId>/policy/<policyId>/statement" -H "x-api-key: $MAISON_API_KEY" -H "content-type: application/json" -d '{ "permission": … }'
json
// MCP (https://maison-labs.com/mcp, Streamable HTTP): tools/call
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "add_client_policy_statement", "arguments": { "permission": … } } }
json
// A2A (https://maison-labs.com/a2a): tasks/send with a data part
{ "jsonrpc": "2.0", "id": 1, "method": "tasks/send", "params": { "message": { "role": "user", "parts": [{ "type": "data", "data": { "tool": "add_client_policy_statement", "input": { "permission": … } } }] } } }

Errors are the same everywhere: 401 no/invalid key; 403 forbidden with details.missing (the grants you lack — fall back to the composite's parts or stop); 403 not_entitled (the hotel's plan lacks the feature); 404, 409, 413 too_large, 503. Inputs that fail validation return 400 invalid_input with the Zod issues — fix and retry once.