Skip to main content
Glama
RhombusSystems

Rhombus MCP Server

Official

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

TableJSON Schema
NameRequiredDescriptionDefault
requestTypeYesThe type of access control request to make.
doorUuidYesThe UUID of the access controlled door. Required for 'unlock-door'.
userUuidYesThe UUID of the user. Required for 'get-credentials-by-user'.
locationUuidYesThe 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.
lockdownPlanUuidYesThe UUID of the lockdown plan. Required for 'activate-lockdown' and 'deactivate-lockdown'.
includeFieldsYesDot-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.
filterByYesFilter 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

TableJSON Schema
NameRequiredDescriptionDefault
unlockResultNoResult of unlocking a door
accessControlGroupsNoList of access control groups
credentialsNoList of access control credentials for a user
lockdownPlansNoList of lockdown plans
lockdownResultNoResult of activating or deactivating a lockdown
doorScheduleExceptionsNoDoor schedule exceptions
accessGrantsNoList of location access grants. Each grant contains userUuids and groupUuids that have access to the doorUuids in the grant.
remoteUnlockUsersNoUsers who can remotely unlock doors at a location, grouped by permission group. Always present the COMPLETE list of all users to the end user.
errorNoAn error message if the request failed.

Implementation Reference

  • 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" });
    };
  • 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 };
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses the behavior of each mode (e.g., unlock-door is a write operation, get-groups is read-only) and warns about large responses. However, it lacks details on side effects, error handling, or permission requirements, which is a moderate gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with bullet points for modes, making it easy to scan. It front-loads the purpose. While it is lengthy due to the number of modes, every section serves a purpose; minor redundancy in parameter explanations could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (7 parameters, 9 modes) and the existence of an output schema, the description covers modes, required parameters, output filtering, and related tools. It could be more complete by briefly noting what each mode returns, but the output schema reduces that need.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the description adds significant value by clarifying which parameters are required for each mode and providing examples for includeFields and filterBy. This goes beyond the schema's generic descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool manages Rhombus access control operations and enumerates nine specific modes via requestType. It differentiates from sibling tools by explicitly directing users to get-entity-tool, user-tool, and location-tool for UUID lookups, and states that this is the correct tool for remote unlock permissions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance for each mode, including required parameters. It names alternative tools for prerequisite lookups and includes a warning about large responses. However, it does not explicitly state when not to use this tool versus a sibling beyond those mentions, leaving some implicit inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RhombusSystems/rhombus-node-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server