rhombus-node-mcp
Version:
MCP server for Rhombus API
257 lines (256 loc) • 9.87 kB
JavaScript
import { postApi } from "../network/network.js";
const PERMISSION_RANK = {
LIVEONLY: 0,
READONLY: 1,
ADMIN: 2,
};
function hasAtLeastReadonly(perm) {
return PERMISSION_RANK[perm ?? ""] >= PERMISSION_RANK.READONLY;
}
export async function unlockDoor(doorUuid, requestModifiers, sessionId) {
const res = await postApi({
route: "/accesscontrol/unlockAccessControlledDoor",
body: { accessControlledDoorUuid: doorUuid },
modifiers: requestModifiers,
sessionId,
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
return { success: true, doorUuid };
}
export async function getAccessControlGroups(requestModifiers, sessionId) {
const res = await postApi({
route: "/accesscontrol/findAccessControlGroupsByOrg",
body: {},
modifiers: requestModifiers,
sessionId,
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
return (res.groups?.map((group) => ({
uuid: group.uuid ?? undefined,
name: group.name ?? undefined,
description: group.description ?? undefined,
orgUuid: group.orgUuid ?? undefined,
userUuids: group.userUuids?.filter((u) => u !== null) ?? [],
})) ?? []);
}
export async function getCredentialsByUser(userUuid, requestModifiers, sessionId) {
const res = await postApi({
route: "/accesscontrol/findAccessControlCredentialByUser",
body: { userUuid },
modifiers: requestModifiers,
sessionId,
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
return (res.credentials?.map((cred) => ({
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, sessionId) {
const res = await postApi({
route: "/accesscontrol/lockdownPlan/findLockdownPlans",
body: {},
modifiers: requestModifiers,
sessionId,
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
return (res.lockdownPlans?.map((plan) => ({
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, lockdownPlanUuid, requestModifiers, sessionId) {
const res = await postApi({
route: "/accesscontrol/lockdownPlan/activateLockdownForLocation",
body: { locationUuid, lockdownPlanUuids: [lockdownPlanUuid] },
modifiers: requestModifiers,
sessionId,
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
return { success: true, locationUuid, action: "activated" };
}
export async function deactivateLockdown(locationUuid, lockdownPlanUuid, requestModifiers, sessionId) {
const res = await postApi({
route: "/accesscontrol/lockdownPlan/deactivateLockdownForLocation",
body: { locationUuid, lockdownPlanUuids: [lockdownPlanUuid] },
modifiers: requestModifiers,
sessionId,
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
return { success: true, locationUuid, action: "deactivated" };
}
export async function getDoorScheduleExceptions(locationUuid, requestModifiers, sessionId) {
const res = await postApi({
route: "/accesscontrol/doorScheduleException/findExceptionsV2",
body: { locationUuid },
modifiers: requestModifiers,
sessionId,
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
return (res.exceptions?.map((exc) => ({
uuid: exc.uuid ?? undefined,
name: exc.name ?? undefined,
startTime: exc.startTimeMs ?? undefined,
endTime: exc.endTimeMs ?? undefined,
doorUuids: exc.doorUuids?.filter((d) => d !== null) ?? [],
})) ?? []);
}
export async function getAccessGrants(locationUuid, requestModifiers, sessionId) {
const res = locationUuid
? await postApi({
route: "/accesscontrol/findLocationAccessGrantsByLocation",
body: { locationUuid },
modifiers: requestModifiers,
sessionId,
})
: await postApi({
route: "/accesscontrol/findLocationAccessGrantsByOrg",
body: {},
modifiers: requestModifiers,
sessionId,
});
if (res.error) {
throw new Error(JSON.stringify(res));
}
const filterNulls = (arr) => arr?.filter((v) => v !== null) ?? [];
return (res.accessGrants?.map((grant) => ({
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, doorLocationUuid, doorAssociatedCameras) {
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, requestModifiers, sessionId) {
const [permGroupsRes, doorsRes, usersRes] = await Promise.all([
postApi({
route: "/permission/getPermissionGroups",
body: {},
modifiers: requestModifiers,
sessionId,
}),
postApi({
route: "/component/findAccessControlledDoors",
body: {},
modifiers: requestModifiers,
sessionId,
}),
postApi({
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 = {};
for (const [groupUuid, userUuids] of Object.entries(permGroupsRes.groupMembership ?? {})) {
groupMembership[groupUuid] = (userUuids ?? []).filter((u) => u !== null);
}
const userMap = new Map();
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) => door.locationUuid === locationUuid && door.remoteUnlockEnabled === true);
const doorNames = doors.map((d) => d.name ?? "Unknown");
const totalDoors = doorNames.length;
const groupResults = new Map();
const seenUsers = new Set();
for (const door of doors) {
const doorLocationUuid = door.locationUuid ?? "";
const doorName = door.name ?? "Unknown";
const associatedCameras = door.associatedCameras?.filter((c) => 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 = 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 };
}