access-control-tool
Execute Rhombus access control tasks: unlock doors, list groups, get credentials, activate/deactivate lockdowns, retrieve door schedules and access grants.
Instructions
This tool manages Rhombus access control operations including door unlocking, access groups, credentials, lockdown plans, door schedules, and access grants.
It has the following modes of operation, determined by the "requestType" parameter:
unlock-door: Remotely unlock an access controlled door. Requires doorUuid.
get-groups: List all access control groups in the organization.
get-credentials-by-user: List all access control credentials for a specific user. Requires userUuid.
get-lockdown-plans: List all lockdown plans in the organization.
activate-lockdown: Activate a lockdown plan at a location. Requires locationUuid and lockdownPlanUuid.
deactivate-lockdown: Deactivate a lockdown plan at a location. Requires locationUuid and lockdownPlanUuid.
get-door-schedules: Get door schedule exceptions for a location. Requires locationUuid.
get-access-grants: List location access grants (physical badge/card access). Optionally accepts locationUuid to filter by location. Each grant includes userUuids (directly assigned users), groupUuids (assigned access control groups), and doorUuids (the doors this grant provides access to).
get-remote-unlock-users: Get all users who have permission to remotely unlock doors at a location. Requires locationUuid. Returns a list of doors with remote unlock enabled and the users who can unlock each door, based on their permission group roles. This is the correct tool for questions about remote unlock permissions.
Use the get-entity-tool with entityType ACCESS_CONTROL_DOOR to get door UUIDs. Use the user-tool to look up user UUIDs and resolve them to names/emails. Use the location-tool to get location UUIDs.
Output filtering (all tools):
includeFields(string[]): Dot-notation paths to keep in the response (e.g."vehicleEvents.vehicleLicensePlate"). Omit to return all fields.filterBy(array): Predicates to filter array items. Each entry:{field, op, value}where op is one of= != > >= < <= contains. All conditions are ANDed. Example:[{field:"vehicleLicensePlate", op:"=", value:"ABC123"}]WARNING: some tool responses exceed 400k characters — use these params to request only the data you need.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| requestType | Yes | The type of access control request to make. | |
| doorUuid | Yes | The UUID of the access controlled door. Required for 'unlock-door'. | |
| userUuid | Yes | The UUID of the user. Required for 'get-credentials-by-user'. | |
| locationUuid | Yes | The UUID of the location. Required for 'activate-lockdown', 'deactivate-lockdown', 'get-door-schedules', and 'get-remote-unlock-users'. Optional for 'get-access-grants' to filter by location. | |
| lockdownPlanUuid | Yes | The UUID of the lockdown plan. Required for 'activate-lockdown' and 'deactivate-lockdown'. | |
| includeFields | Yes | Dot-notation field paths to include in the response (e.g. "vehicleEvents.vehicleLicensePlate"). Pass null to return all fields. WARNING: some responses can exceed 400k characters — use includeFields to request only the data you need. For high-volume tools this may be required to get a complete answer. | |
| filterBy | Yes | Filter array items in the response by field values. All conditions are ANDed. Example: [{field: "vehicleLicensePlate", op: "=", value: "ABC123"}, {field: "confidence", op: ">", value: 0.8}] Use alongside includeFields to get only the specific records and fields you need. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| unlockResult | No | Result of unlocking a door | |
| accessControlGroups | No | List of access control groups | |
| credentials | No | List of access control credentials for a user | |
| lockdownPlans | No | List of lockdown plans | |
| lockdownResult | No | Result of activating or deactivating a lockdown | |
| doorScheduleExceptions | No | Door schedule exceptions | |
| accessGrants | No | List of location access grants. Each grant contains userUuids and groupUuids that have access to the doorUuids in the grant. | |
| remoteUnlockUsers | No | Users who can remotely unlock doors at a location, grouped by permission group. Always present the COMPLETE list of all users to the end user. | |
| error | No | An error message if the request failed. |
Implementation Reference
- src/tools/access-control-tool.ts:46-134 (handler)The main handler function for the access-control-tool. It switches on requestType (unlock-door, get-groups, get-credentials-by-user, get-lockdown-plans, activate-lockdown, deactivate-lockdown, get-door-schedules, get-access-grants, get-remote-unlock-users) and calls the corresponding API function. Returns structured content or error messages.
const TOOL_HANDLER = async (args: ToolArgs, _extra: unknown) => { const { requestModifiers, sessionId } = extractFromToolExtra(_extra); try { switch (args.requestType) { case AccessControlRequestType.UNLOCK_DOOR: { if (!args.doorUuid) { return createToolTextContent( JSON.stringify({ error: "doorUuid is required for unlock-door." }) ); } const unlockResult = await unlockDoor(args.doorUuid, requestModifiers, sessionId); return createToolStructuredContent<OUTPUT_SCHEMA>({ unlockResult }); } case AccessControlRequestType.GET_GROUPS: { const accessControlGroups = await getAccessControlGroups(requestModifiers, sessionId); return createToolStructuredContent<OUTPUT_SCHEMA>({ accessControlGroups }); } case AccessControlRequestType.GET_CREDENTIALS_BY_USER: { if (!args.userUuid) { return createToolTextContent( JSON.stringify({ error: "userUuid is required for get-credentials-by-user." }) ); } const credentials = await getCredentialsByUser(args.userUuid, requestModifiers, sessionId); return createToolStructuredContent<OUTPUT_SCHEMA>({ credentials }); } case AccessControlRequestType.GET_LOCKDOWN_PLANS: { const lockdownPlans = await getLockdownPlans(requestModifiers, sessionId); return createToolStructuredContent<OUTPUT_SCHEMA>({ lockdownPlans }); } case AccessControlRequestType.ACTIVATE_LOCKDOWN: { if (!args.locationUuid || !args.lockdownPlanUuid) { return createToolTextContent( JSON.stringify({ error: "locationUuid and lockdownPlanUuid are required for activate-lockdown." }) ); } const lockdownResult = await activateLockdown( args.locationUuid, args.lockdownPlanUuid, requestModifiers, sessionId ); return createToolStructuredContent<OUTPUT_SCHEMA>({ lockdownResult }); } case AccessControlRequestType.DEACTIVATE_LOCKDOWN: { if (!args.locationUuid || !args.lockdownPlanUuid) { return createToolTextContent( JSON.stringify({ error: "locationUuid and lockdownPlanUuid are required for deactivate-lockdown." }) ); } const lockdownResult = await deactivateLockdown( args.locationUuid, args.lockdownPlanUuid, requestModifiers, sessionId ); return createToolStructuredContent<OUTPUT_SCHEMA>({ lockdownResult }); } case AccessControlRequestType.GET_DOOR_SCHEDULES: { if (!args.locationUuid) { return createToolTextContent( JSON.stringify({ error: "locationUuid is required for get-door-schedules." }) ); } const doorScheduleExceptions = await getDoorScheduleExceptions( args.locationUuid, requestModifiers, sessionId ); return createToolStructuredContent<OUTPUT_SCHEMA>({ doorScheduleExceptions }); } case AccessControlRequestType.GET_ACCESS_GRANTS: { const accessGrants = await getAccessGrants(args.locationUuid, requestModifiers, sessionId); return createToolStructuredContent<OUTPUT_SCHEMA>({ accessGrants }); } case AccessControlRequestType.GET_REMOTE_UNLOCK_USERS: { if (!args.locationUuid) { return createToolTextContent( JSON.stringify({ error: "locationUuid is required for get-remote-unlock-users." }) ); } const remoteUnlockUsers = await getRemoteUnlockUsers( args.locationUuid, requestModifiers, sessionId ); return createToolStructuredContent<OUTPUT_SCHEMA>({ remoteUnlockUsers }); } } } catch (error: unknown) { if (error instanceof Error) { return createToolStructuredContent<OUTPUT_SCHEMA>({ error: error.message }); } return createToolStructuredContent<OUTPUT_SCHEMA>({ error: "Unknown error" }); } return createToolStructuredContent({ error: "Invalid request type" }); }; - src/tools/access-control-tool.ts:136-146 (registration)Registration function 'createTool' that registers the tool with the MCP server using TOOL_NAME ('access-control-tool'), the input schema (TOOL_ARGS), output schema (OUTPUT_SCHEMA.shape), and the handler function. Called dynamically by getTools.ts -> createServer.ts.
export function createTool(server: McpServer) { server.registerTool( TOOL_NAME, { description: TOOL_DESCRIPTION, inputSchema: TOOL_ARGS, outputSchema: OUTPUT_SCHEMA.shape, }, TOOL_HANDLER ); } - Input/output type definitions for the access-control-tool. Defines the AccessControlRequestType enum (9 modes), TOOL_ARGS Zod schema (requestType, doorUuid, userUuid, locationUuid, lockdownPlanUuid), and OUTPUT_SCHEMA Zod schema (all possible response shapes).
import { z } from "zod"; import { createUuidSchema } from "../types.js"; import { INCLUDE_FIELDS_ARG, FILTER_BY_ARG } from "../util.js"; export enum AccessControlRequestType { UNLOCK_DOOR = "unlock-door", GET_GROUPS = "get-groups", GET_CREDENTIALS_BY_USER = "get-credentials-by-user", GET_LOCKDOWN_PLANS = "get-lockdown-plans", ACTIVATE_LOCKDOWN = "activate-lockdown", DEACTIVATE_LOCKDOWN = "deactivate-lockdown", GET_DOOR_SCHEDULES = "get-door-schedules", GET_ACCESS_GRANTS = "get-access-grants", GET_REMOTE_UNLOCK_USERS = "get-remote-unlock-users", } export const TOOL_ARGS = { requestType: z.nativeEnum(AccessControlRequestType).describe("The type of access control request to make."), doorUuid: z .string() .nullable() .describe("The UUID of the access controlled door. Required for 'unlock-door'."), userUuid: z .string() .nullable() .describe("The UUID of the user. Required for 'get-credentials-by-user'."), locationUuid: z .string() .nullable() .describe("The UUID of the location. Required for 'activate-lockdown', 'deactivate-lockdown', 'get-door-schedules', and 'get-remote-unlock-users'. Optional for 'get-access-grants' to filter by location."), lockdownPlanUuid: z .string() .nullable() .describe("The UUID of the lockdown plan. Required for 'activate-lockdown' and 'deactivate-lockdown'."), includeFields: INCLUDE_FIELDS_ARG, filterBy: FILTER_BY_ARG, }; const TOOL_ARGS_SCHEMA = z.object(TOOL_ARGS); export type ToolArgs = z.infer<typeof TOOL_ARGS_SCHEMA>; export const OUTPUT_SCHEMA = z.object({ unlockResult: z .object({ success: z.boolean().optional(), doorUuid: z.string().optional(), }) .optional() .describe("Result of unlocking a door"), accessControlGroups: z .array( z.object({ uuid: z.string().optional(), name: z.string().optional(), description: z.string().optional(), orgUuid: z.string().optional(), userUuids: z.array(z.string()).optional(), }) ) .optional() .describe("List of access control groups"), credentials: z .array( z.object({ uuid: z.string().optional(), userUuid: z.string().optional(), credentialType: z.string().optional(), status: z.string().optional(), note: z.string().optional(), }) ) .optional() .describe("List of access control credentials for a user"), lockdownPlans: z .array( z.object({ uuid: z.string().optional(), name: z.string().optional(), locationUuid: z.string().optional(), description: z.string().optional(), active: z.boolean().optional(), }) ) .optional() .describe("List of lockdown plans"), lockdownResult: z .object({ success: z.boolean().optional(), locationUuid: z.string().optional(), action: z.string().optional(), }) .optional() .describe("Result of activating or deactivating a lockdown"), doorScheduleExceptions: z .array( z.object({ uuid: z.string().optional(), name: z.string().optional(), startTime: z.number().optional(), endTime: z.number().optional(), doorUuids: z.array(z.string()).optional(), }) ) .optional() .describe("Door schedule exceptions"), accessGrants: z .array( z.object({ uuid: z.string().optional(), name: z.string().optional(), locationUuid: z.string().optional(), userUuids: z.array(z.string()).optional(), groupUuids: z.array(z.string()).optional(), doorUuids: z.array(z.string()).optional(), scheduleUuid: z.string().optional(), }) ) .optional() .describe("List of location access grants. Each grant contains userUuids and groupUuids that have access to the doorUuids in the grant."), remoteUnlockUsers: z .object({ doors: z.array(z.string()).optional().describe("Names of doors with remote unlock enabled at this location."), totalUsers: z.number().optional().describe("Total number of unique users who can remotely unlock doors."), groups: z.array( z.object({ permissionGroup: z.string().optional().describe("Name of the permission group/role."), doors: z.union([z.literal("all"), z.array(z.string())]).optional() .describe("Which doors users in this group can unlock. 'all' means every door at the location."), users: z.array(z.string()).optional() .describe("Users in this group, formatted as 'Name (email)'. Always list ALL users completely."), }) ).optional(), }) .optional() .describe("Users who can remotely unlock doors at a location, grouped by permission group. Always present the COMPLETE list of all users to the end user."), error: z.string().optional().describe("An error message if the request failed."), }); export type OUTPUT_SCHEMA = z.infer<typeof OUTPUT_SCHEMA>; - API helper layer containing all the backend API calls for access control operations: unlockDoor, getAccessControlGroups, getCredentialsByUser, getLockdownPlans, activateLockdown, deactivateLockdown, getDoorScheduleExceptions, getAccessGrants, and getRemoteUnlockUsers (with complex permission group logic).
import { postApi } from "../network/network.js"; import type { schema } from "../types/schema.js"; import type { RequestModifiers } from "../util.js"; const PERMISSION_RANK: Record<string, number> = { LIVEONLY: 0, READONLY: 1, ADMIN: 2, }; function hasAtLeastReadonly(perm: string | undefined | null): boolean { return PERMISSION_RANK[perm ?? ""] >= PERMISSION_RANK.READONLY; } export async function unlockDoor( doorUuid: string, requestModifiers?: RequestModifiers, sessionId?: string ) { const res = await postApi<schema["Accesscontrol_credentials_BaseUnlockAccessControlledDoorWSResponse"]>({ route: "/accesscontrol/unlockAccessControlledDoor", body: { accessControlledDoorUuid: doorUuid } satisfies schema["Accesscontrol_UnlockAccessControlledDoorWSRequest"], modifiers: requestModifiers, sessionId, }); if (res.error) { throw new Error(JSON.stringify(res)); } return { success: true, doorUuid }; } export async function getAccessControlGroups( requestModifiers?: RequestModifiers, sessionId?: string ) { const res = await postApi<schema["Group_FindOrgGroupsByOrgWSResponse"]>({ route: "/accesscontrol/findAccessControlGroupsByOrg", body: {} satisfies schema["Group_FindOrgGroupsByOrgWSRequest"], modifiers: requestModifiers, sessionId, }); if (res.error) { throw new Error(JSON.stringify(res)); } return ( (res as any).groups?.map((group: any) => ({ uuid: group.uuid ?? undefined, name: group.name ?? undefined, description: group.description ?? undefined, orgUuid: group.orgUuid ?? undefined, userUuids: group.userUuids?.filter((u: any): u is string => u !== null) ?? [], })) ?? [] ); } export async function getCredentialsByUser( userUuid: string, requestModifiers?: RequestModifiers, sessionId?: string ) { const res = await postApi<schema["Accesscontrol_credentials_FindAccessControlCredentialByUserWSResponse"]>({ route: "/accesscontrol/findAccessControlCredentialByUser", body: { userUuid } satisfies schema["Accesscontrol_credentials_FindAccessControlCredentialByUserWSRequest"], modifiers: requestModifiers, sessionId, }); if (res.error) { throw new Error(JSON.stringify(res)); } return ( res.credentials?.map((cred: any) => ({ uuid: cred.uuid ?? undefined, userUuid: cred.userUuid ?? undefined, credentialType: cred.credentialType ?? undefined, status: cred.workflowStatus ?? undefined, note: cred.note ?? undefined, })) ?? [] ); } export async function getLockdownPlans( requestModifiers?: RequestModifiers, sessionId?: string ) { const res = await postApi<schema["Accesscontrol_lockdownplan_FindLockdownPlansWSResponse"]>({ route: "/accesscontrol/lockdownPlan/findLockdownPlans", body: {} satisfies schema["Accesscontrol_lockdownplan_FindLockdownPlansWSRequest"], modifiers: requestModifiers, sessionId, }); if (res.error) { throw new Error(JSON.stringify(res)); } return ( res.lockdownPlans?.map((plan: any) => ({ uuid: plan.uuid ?? undefined, name: plan.name ?? undefined, locationUuid: plan.locationUuid ?? undefined, description: plan.description ?? undefined, active: plan.active ?? undefined, })) ?? [] ); } export async function activateLockdown( locationUuid: string, lockdownPlanUuid: string, requestModifiers?: RequestModifiers, sessionId?: string ) { const res = await postApi<schema["Accesscontrol_lockdownplan_ActivateLockdownForLocationWSResponse"]>({ route: "/accesscontrol/lockdownPlan/activateLockdownForLocation", body: { locationUuid, lockdownPlanUuids: [lockdownPlanUuid] } as any, modifiers: requestModifiers, sessionId, }); if (res.error) { throw new Error(JSON.stringify(res)); } return { success: true, locationUuid, action: "activated" }; } export async function deactivateLockdown( locationUuid: string, lockdownPlanUuid: string, requestModifiers?: RequestModifiers, sessionId?: string ) { const res = await postApi<schema["Accesscontrol_lockdownplan_DeactivateLockdownForLocationWSResponse"]>({ route: "/accesscontrol/lockdownPlan/deactivateLockdownForLocation", body: { locationUuid, lockdownPlanUuids: [lockdownPlanUuid] } as any, modifiers: requestModifiers, sessionId, }); if (res.error) { throw new Error(JSON.stringify(res)); } return { success: true, locationUuid, action: "deactivated" }; } export async function getDoorScheduleExceptions( locationUuid: string, requestModifiers?: RequestModifiers, sessionId?: string ) { const res = await postApi<schema["Accesscontrol_doorexception_FindDoorScheduleExceptionsWSResponse"]>({ route: "/accesscontrol/doorScheduleException/findExceptionsV2", body: { locationUuid } as any, modifiers: requestModifiers, sessionId, }); if (res.error) { throw new Error(JSON.stringify(res)); } return ( res.exceptions?.map((exc: any) => ({ uuid: exc.uuid ?? undefined, name: exc.name ?? undefined, startTime: exc.startTimeMs ?? undefined, endTime: exc.endTimeMs ?? undefined, doorUuids: exc.doorUuids?.filter((d: any): d is string => d !== null) ?? [], })) ?? [] ); } export async function getAccessGrants( locationUuid?: string | null, requestModifiers?: RequestModifiers, sessionId?: string ) { const res = locationUuid ? await postApi<schema["Accesscontrol_accessgrant_FindLocationAccessGrantsByLocationWSResponse"]>({ route: "/accesscontrol/findLocationAccessGrantsByLocation", body: { locationUuid } satisfies schema["Accesscontrol_accessgrant_FindLocationAccessGrantsByLocationWSRequest"], modifiers: requestModifiers, sessionId, }) : await postApi<schema["Accesscontrol_accessgrant_FindLocationAccessGrantsByOrgWSResponse"]>({ route: "/accesscontrol/findLocationAccessGrantsByOrg", body: {} satisfies schema["Accesscontrol_accessgrant_FindLocationAccessGrantsByOrgWSRequest"], modifiers: requestModifiers, sessionId, }); if (res.error) { throw new Error(JSON.stringify(res)); } const filterNulls = (arr?: (string | null)[] | null): string[] => arr?.filter((v): v is string => v !== null) ?? []; return ( (res as any).accessGrants?.map((grant: any) => ({ uuid: grant.uuid ?? undefined, name: grant.name ?? undefined, locationUuid: grant.locationUuid ?? undefined, userUuids: filterNulls(grant.userUuids), groupUuids: filterNulls(grant.groupUuids), doorUuids: filterNulls(grant.accessControlledDoorUuids), scheduleUuid: grant.scheduleUuid ?? undefined, })) ?? [] ); } function canRoleUnlockDoor( role: any, doorLocationUuid: string, doorAssociatedCameras: string[] ): boolean { if (role.superAdmin) return true; const hasDoorAdmin = role.functionalityList?.includes("DOOR_ACCESS_ADMINISTRATION"); if (!hasDoorAdmin) return false; const acMap = role.accessControlLocationAccessMap ?? {}; const granularMap = role.locationGranularAccessMap ?? {}; const acLocationPerm = acMap[doorLocationUuid]; const granularLocationPerms = granularMap[doorLocationUuid] ?? {}; const accessConditionsPerm = granularLocationPerms["ACCESS_CONDITIONS"]; if (hasAtLeastReadonly(acLocationPerm) && hasAtLeastReadonly(accessConditionsPerm)) { return true; } if (doorAssociatedCameras.length > 0) { const locationMap = role.locationAccessMap ?? {}; const deviceMap = role.deviceAccessMap ?? {}; if (hasAtLeastReadonly(locationMap[doorLocationUuid])) return true; for (const cameraUuid of doorAssociatedCameras) { if (hasAtLeastReadonly(deviceMap[cameraUuid])) return true; } } return false; } export async function getRemoteUnlockUsers( locationUuid: string, requestModifiers?: RequestModifiers, sessionId?: string ) { const [permGroupsRes, doorsRes, usersRes] = await Promise.all([ postApi<schema["Permission_GetPermissionGroupsWSResponse"]>({ route: "/permission/getPermissionGroups", body: {} satisfies schema["Permission_GetPermissionGroupsWSRequest"], modifiers: requestModifiers, sessionId, }), postApi<schema["Component_FindAccessControlledDoorsWSResponse"]>({ route: "/component/findAccessControlledDoors", body: {}, modifiers: requestModifiers, sessionId, }), postApi<schema["User_GetUsersInOrgWSResponse"]>({ route: "/user/getUsersInOrg", body: {}, modifiers: requestModifiers, sessionId, }), ]); if (permGroupsRes.error) throw new Error(JSON.stringify(permGroupsRes)); if (doorsRes.error) throw new Error(JSON.stringify(doorsRes)); if (usersRes.error) throw new Error(JSON.stringify(usersRes)); const permissionGroups = permGroupsRes.permissionGroups ?? []; const groupMembership: Record<string, string[]> = {}; for (const [groupUuid, userUuids] of Object.entries(permGroupsRes.groupMembership ?? {})) { groupMembership[groupUuid] = (userUuids ?? []).filter((u): u is string => u !== null); } const userMap = new Map<string, { uuid: string; firstName?: string; lastName?: string; email?: string }>(); for (const user of usersRes.users ?? []) { if (user.uuid) { userMap.set(user.uuid, { uuid: user.uuid, firstName: user.firstName ?? undefined, lastName: user.lastName ?? undefined, email: user.email ?? undefined, }); } } const doors = (doorsRes.accessControlledDoors ?? []).filter( (door: any) => door.locationUuid === locationUuid && door.remoteUnlockEnabled === true ); const doorNames = doors.map((d: any) => d.name ?? "Unknown"); const totalDoors = doorNames.length; type GroupResult = { permissionGroup: string; doors: "all" | string[]; users: string[]; }; const groupResults = new Map<string, { doorNames: Set<string>; userEntries: Set<string> }>(); const seenUsers = new Set<string>(); for (const door of doors) { const doorLocationUuid = door.locationUuid ?? ""; const doorName = door.name ?? "Unknown"; const associatedCameras: string[] = door.associatedCameras?.filter((c: any): c is string => c !== null) ?? []; for (const role of permissionGroups) { if (!role.uuid) continue; if (!canRoleUnlockDoor(role, doorLocationUuid, associatedCameras)) continue; const roleName = role.name ?? "Unknown"; let group = groupResults.get(roleName); if (!group) { group = { doorNames: new Set(), userEntries: new Set() }; groupResults.set(roleName, group); } group.doorNames.add(doorName); for (const userUuid of groupMembership[role.uuid] ?? []) { if (!userMap.has(userUuid) || seenUsers.has(userUuid)) continue; seenUsers.add(userUuid); const user = userMap.get(userUuid)!; const name = [user.firstName, user.lastName].filter(Boolean).join(" "); const label = name ? `${name} (${user.email ?? "no email"})` : (user.email ?? userUuid); group.userEntries.add(label); } } } const groups: GroupResult[] = Array.from(groupResults.entries()).map(([name, g]) => ({ permissionGroup: name, doors: g.doorNames.size === totalDoors ? "all" : Array.from(g.doorNames), users: Array.from(g.userEntries), })); const totalUsers = seenUsers.size; return { doors: doorNames, totalUsers, groups }; }