rhombus-node-mcp
Version:
MCP server for Rhombus API
77 lines (76 loc) • 3.54 kB
JavaScript
import { z } from "zod";
import { createUuidSchema } from "../types.js";
import { ISOTimestampFormatDescription } from "../utils/timestampInput.js";
export const ANOMALY_RULES = [
"lost_or_inactive_badge",
"entry_not_made",
"off_hours",
"impossible_travel",
"area_novelty",
];
export const TOOL_ARGS = {
area: z.string().nullable().describe("Optional: restrict analysis to events entering this area (full-text)."),
locationUuids: z
.array(createUuidSchema())
.nullable()
.describe("Optional: restrict to these Rhombus location UUIDs."),
deviceUuids: z.array(createUuidSchema()).nullable().describe("Optional: restrict to these camera UUIDs."),
startTime: z
.string()
.datetime({ message: "Invalid datetime string. Expected ISO 8601 format.", offset: true })
.nullable()
.describe("Start of the window to analyze (inclusive). " + ISOTimestampFormatDescription),
endTime: z
.string()
.datetime({ message: "Invalid datetime string. Expected ISO 8601 format.", offset: true })
.nullable()
.describe("End of the window to analyze (inclusive). " + ISOTimestampFormatDescription),
rules: z
.array(z.enum(ANOMALY_RULES))
.nullable()
.describe("Which rules to run; default all. Options: lost_or_inactive_badge, entry_not_made, off_hours, impossible_travel, area_novelty."),
baselineDays: z
.number()
.nullable()
.describe("Days of prior history used to learn each person's normal areas for area_novelty (default 30; 0 disables it)."),
offHoursStartHour: z
.number()
.nullable()
.describe("Local business-hours start hour 0–23; entries before it are off-hours (default 7)."),
offHoursEndHour: z
.number()
.nullable()
.describe("Local business-hours end hour 0–23; entries at/after it are off-hours (default 19)."),
impossibleTravelMaxSeconds: z
.number()
.nullable()
.describe("Max seconds between two different-area taps by one person to flag impossible travel (default 30)."),
limit: z.number().nullable().describe("Max events to analyze in the window (default 500)."),
timeZone: z
.string()
.nullable()
.describe("IANA timezone for hour-of-day checks and formatting, 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() });
const StillHintSchema = z.object({ deviceUuid: z.string(), timestampMs: z.number() });
export const AnomalyFindingSchema = z.object({
cardholderName: z.string().optional(),
rule: z.string().describe("Which rule fired."),
severity: z.string().describe('"high" or "medium".'),
datetime: z.string().optional(),
timestampMs: z.number().optional(),
deviceUuid: z.string().optional().describe("Camera at the event. Pass to camera-tool (image) / clips-tool (createClip)."),
area: z.string().optional(),
rationale: z.string().describe("Plain-language explanation of why this was flagged."),
clipHint: ClipHintSchema.optional(),
stillHint: StillHintSchema.optional(),
});
export const OUTPUT_SCHEMA = z.object({
findings: z
.array(AnomalyFindingSchema)
.optional()
.describe("Anomalies, ranked high severity first then most-recent."),
eventsAnalyzed: z.number().optional(),
error: z.string().optional(),
});