rhombus-node-mcp
Version:
MCP server for Rhombus API
89 lines (88 loc) • 4.45 kB
JavaScript
import { z } from "zod";
import { createUuidSchema } from "../types.js";
import { ISOTimestampFormatDescription } from "../utils/timestampInput.js";
export const TOOL_ARGS = {
personQuery: z
.string()
.describe('The person to track, full-text name match against their access-control (badge) records, e.g. "Brandon" or "Brandon Salzberg".'),
startTime: z
.string()
.datetime({ message: "Invalid datetime string. Expected ISO 8601 format.", offset: true })
.nullable()
.describe("Start of the window to search badge taps and track over (inclusive). " + ISOTimestampFormatDescription),
endTime: z
.string()
.datetime({ message: "Invalid datetime string. Expected ISO 8601 format.", offset: true })
.nullable()
.describe("End of the window (inclusive). " + ISOTimestampFormatDescription),
locationUuids: z
.array(createUuidSchema())
.nullable()
.describe("Optional: restrict badge search and the re-id track to these Rhombus location UUIDs."),
badgeMatchWindowSeconds: z
.number()
.nullable()
.describe("± seconds around the badge tap to look on the door camera for the person's re-id embedding (default 30)."),
clipPaddingSeconds: z
.number()
.nullable()
.describe("Seconds of video before/after each sighting to include in the clip hint (default 15)."),
limit: z.number().nullable().describe("Maximum sightings to include (default 200)."),
timeZone: z
.string()
.nullable()
.describe("IANA timezone used to format times, e.g. America/New_York. Defaults to UTC."),
};
const TOOL_ARGS_SCHEMA = z.object(TOOL_ARGS);
const ClipHintSchema = z
.object({ deviceUuid: z.string(), startTimeMs: z.number(), endTimeMs: z.number() })
.describe("Pass to clips-tool createClip to get video of this sighting.");
const StillHintSchema = z
.object({ deviceUuid: z.string(), timestampMs: z.number() })
.describe("Pass to camera-tool (requestType image) to get a still of this sighting.");
export const SightingSchema = z.object({
timestampMs: z.number().optional(),
datetime: z.string().optional().describe("Human-readable sighting time in the requested timezone."),
deviceUuid: z.string().optional().describe("The camera that re-identified the person."),
locationUuid: z.string().optional(),
distance: z
.number()
.optional()
.describe("Re-id match distance to the door embedding — LOWER means a closer appearance match."),
stableTrackId: z.number().optional().describe("Per-camera track-consolidation id for this detection."),
thumbnailUri: z.string().optional().describe("Thumbnail of the detected person."),
clipHint: ClipHintSchema.optional(),
stillHint: StillHintSchema.optional(),
gapToNextSeconds: z
.number()
.optional()
.describe("Seconds until the next sighting — large gaps mean the person was unobserved between cameras."),
});
const AnchorSchema = z
.object({
deviceUuid: z.string().optional().describe("The door camera where the badge tap happened."),
timestampMs: z.number().optional(),
datetime: z.string().optional(),
integration: z.string().optional().describe("Which badge system the tap came from (OnGuard / Elements / NetBox)."),
area: z.string().optional(),
})
.describe("The access-control badge tap used to ground the re-id track (the known identity moment).");
export const OUTPUT_SCHEMA = z.object({
resolvedPerson: z
.object({ name: z.string().optional() })
.optional()
.describe("The person resolved from the badge record."),
anchor: AnchorSchema.optional(),
sightings: z
.array(SightingSchema)
.optional()
.describe("Re-id sightings of the person across cameras, in chronological order (oldest first)."),
path: z
.array(z.string())
.optional()
.describe("Camera UUIDs the person was re-identified at, in order (consecutive repeats collapsed)."),
lastKnownSighting: SightingSchema.optional().describe("The most recent re-id sighting — last-known location."),
count: z.number().optional().describe("Number of re-id sightings returned."),
note: z.string().optional().describe("Set when the track couldn't be built (no badge tap, or no re-id at the door)."),
error: z.string().optional(),
});