Agent Skills / maison-access
Install this skill alone, or read it below. Raw file: SKILL.md · references/tools.md
npx skills add https://docs.maison-labs.com --skill maison-accessMaison — 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) orset_client_policy_rules(hotel policy) save the full rule list in one call. - Create a reusable permission bundle for a hotel —
create_client_policywith statements, attach it to a role withattach_client_role_policy. - Explain a 403 —
get_client_user_access(users skill) orget_client_role/get_client_policyeffective access. - Roles and groups available to all clients —
create_role(clientId null),create_group,update_group_roles; apply a shared policy at chosen hotels withattach_role_policyand change it withset_role_policy_applies_to. - Bring a retired role back —
restore_role. Page a role's holders pastget_role's first 50 —list_role_users,list_role_groups. - Maison policies end to end —
create_policy,set_policy_rules,attach_role_policy;update_policyrenames or re-describes;delete_policyonce no role applies it. - Rename a hotel group or drop it —
update_client_group,delete_client_group.
Before you start
- Send
x-api-key: <key>on every call; the key only reaches/api/v1. GET /api/v1/tools(or MCPtools/list) returns the tools THIS key may run — if a tool below is missing there, you lack a grant; do not retry it.- Client-scoped tools take
clientId(a hotel id).whoamilists the ids you can act on.
Tools
| Tool | What it does | REST | Needs |
|---|---|---|---|
add_client_policy_statement | Set 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}/statement | policies WRITE (client) |
add_client_role_rule | Set 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}/rule | maison-roles WRITE (client), policies WRITE (client) |
add_role_policy | Set 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}/policy | maison-roles WRITE (global), policies WRITE (global) |
assign_role | Give a user this role at one client (clientId) or at all clients (null). | POST /api/v1/role/{roleId}/user | maison-users WRITE (client) |
assign_role_to_group | Give a group this role at one client (clientId) or at all clients (null) so its members inherit it. | POST /api/v1/role/{roleId}/group | maison-users WRITE (client) |
attach_client_role_policy | Attach an existing policy to a hotel role; the role applies it at this hotel. | POST /api/v1/client/{clientId}/role/{roleId}/policy | maison-roles WRITE (client) |
attach_role_policy | Attach 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}/attach | maison-roles WRITE (any) |
create_client_group | Create a new access group owned by a hotel and optionally seed it with members and a role. | POST /api/v1/client/{clientId}/group | groups WRITE (client); composite of update_client_group_members, link_client_group_role |
create_client_policy | Create a policy available to one hotel and optionally seed it with rules (each a feature and a level). | POST /api/v1/client/{clientId}/policy | policies WRITE (client); composite of add_client_policy_statement |
create_client_role | Create 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}/role | maison-roles WRITE (client); composite of add_client_role_rule |
create_group | Create 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/group | groups WRITE (global); composite of update_group_members, update_group_roles |
create_policy | Create 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/policy | policies WRITE (global) |
create_role | Create 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/role | maison-roles WRITE (client); composite of add_role_policy |
delete_client_group | Delete (soft) a group the hotel owns, with its role links. | DELETE /api/v1/client/{clientId}/group/{groupId} | groups WRITE (client); destructive |
delete_client_policy | Soft-delete a hotel-owned policy. | DELETE /api/v1/client/{clientId}/policy/{policyId} | policies WRITE (client); destructive |
delete_group | Soft-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_policy | Delete (soft) a policy available to all clients. | DELETE /api/v1/policy/{policyId} | policies WRITE (global); destructive |
detach_client_role_policy | Stop a hotel role applying a policy. | DELETE /api/v1/client/{clientId}/role/{roleId}/policy/{policyId} | maison-roles WRITE (client); destructive |
detach_role_policy | Stop a role applying a policy anywhere (every client it applied at). | POST /api/v1/role/{roleId}/policy/{policyId}/detach | maison-roles WRITE (any); destructive |
disable_client_role | Soft-disable a hotel-owned role. | POST /api/v1/client/{clientId}/role/{roleId}/disable | maison-roles WRITE (client); destructive |
disable_role | Retire a role (sets disabled_date); needs the roles grant where the role is available. | POST /api/v1/role/{roleId}/disable | maison-roles WRITE (any); destructive |
get_client_group | Fetch 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_policy | Fetch 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_role | Fetch a hotel role's policies and its effective access summary. | GET /api/v1/client/{clientId}/role/{roleId} | maison-roles READ (client); read-only |
get_group | Full detail of a group you can see, including members and role assignments. | GET /api/v1/group/{groupId} | groups READ (any); read-only |
get_policy | Fetch 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_role | Fetch 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_role | Create a user_group_role_link so a hotel-owned group inherits a role at this hotel. | POST /api/v1/client/{clientId}/usergroup | groups WRITE (client), maison-users WRITE (client) |
list_client_features_catalogue | Features available for hotel-scoped roles and policies at a specific hotel, with their display labels. | GET /api/v1/client/{clientId}/featurescatalogue | policies READ (client); read-only |
list_client_groups | The access groups available at a hotel: its own and those available to all clients — never another hotel's. | GET /api/v1/client/{clientId}/groups | groups READ (client); read-only |
list_client_policies | The 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}/policies | policies READ (client); read-only |
list_client_roles | Roles that can be assigned to users or groups at a specific hotel (excludes Maison-only and disabled roles). | GET /api/v1/client/{clientId}/roles | maison-roles READ (client); read-only |
list_features | Read-only catalogue of all Maison features: id, key (name), display label, purpose (what it does and where), description and maison_only. | GET /api/v1/features | maison-roles READ (global); read-only |
list_groups | Page 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/groups | groups READ (any); read-only |
list_policies | Page 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/policies | policies READ (any); read-only |
list_role_groups | Page through the groups given a role (their members inherit it), each with the client the link is at. | GET /api/v1/role/{roleId}/groups | maison-roles READ (any); read-only |
list_role_users | Page through the users holding a role directly, each with the client the link is at (null = all clients). | GET /api/v1/role/{roleId}/users | maison-roles READ (any); read-only |
list_roles | Page 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/roles | maison-roles READ (any); read-only |
remove_client_policy_statement | Soft-delete one statement from a hotel-owned policy. | DELETE /api/v1/client/{clientId}/policy/{policyId}/statement/{stmtId} | policies WRITE (client); destructive |
remove_client_role_rule | Remove 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_policy | Soft-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_role | Bring 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}/restore | maison-roles WRITE (any) |
set_client_policy_rules | Save 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}/rules | policies WRITE (client) |
set_policy_rules | Save 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}/rules | policies WRITE (global) |
set_role_policy_applies_to | Replace 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}/appliesto | maison-roles WRITE (any) |
unassign_role | Take 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_group | Soft-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_group | Rename 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_members | Add or remove members in a hotel-owned access group. | PUT /api/v1/client/{clientId}/group/{groupId}/members | groups WRITE (client) |
update_client_policy | Patch a hotel-owned policy's name or description. | PUT /api/v1/client/{clientId}/policy/{policyId} | policies WRITE (client) |
update_client_role | Patch a hotel-owned role's name or description. | PUT /api/v1/client/{clientId}/role/{roleId} | maison-roles WRITE (client) |
update_group | Patch 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_members | Add 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}/members | groups WRITE (any) |
update_group_roles | Add 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}/roles | groups WRITE (any), maison-users WRITE (any) |
update_policy | Rename 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_role | Patch 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
get_policy(orget_client_policy) for the current rules;list_features(orlist_client_features_catalogue) for each feature's id, label and purpose.- 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.
- Ambiguous (which features, which level)? Show the rules as a short Feature / Level list and ask before saving.
set_policy_rules(orset_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
list_client_features_catalogueto see which features the hotel's plan allows for roles.create_client_policywithstatements: [{ featureId, permission }](orcreate_client_rolewith inline rules; add or remove one later withadd_client_role_rule/remove_client_role_rule); a rule names no client.create_client_groupwithmemberIdsandroleId, or assign per user withset_client_user_roles.- Verify with
get_client_group/get_client_roleeffective 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 needallow-maison-onlyWRITE held globally and by name — an ALL wildcard never counts (403 namesallow-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_ruleandadd_client_policy_statementset 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.clientsWRITE grants nothing in access management. Read with the same feature at READ. - A role's own inline rules (
add_role_policy,create_rolewithpolicies) need GLOBALpoliciesWRITE; a hotel's rules (set_client_policy_rules,add_client_policy_statement) needpoliciesWRITE on the hotel. - A policy available to one hotel applies only there; a role available to one hotel applies its policies there only (
appliesTonull). 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_groupis 409 while the group has members (pending invitations included) anddelete_policyis 409 while a role applies it: empty or detach first.unassign_rolewithoutlinkScoperemoves the role at every scope; passlinkScope: "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.
# 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": … }'// 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": … } } }// 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.