@butlr/butlr-mcp-server 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cache/topology-cache.d.ts +18 -3
  2. package/dist/cache/topology-cache.d.ts.map +1 -1
  3. package/dist/cache/topology-cache.js +19 -3
  4. package/dist/cache/topology-cache.js.map +1 -1
  5. package/dist/clients/queries/tags.d.ts +63 -6
  6. package/dist/clients/queries/tags.d.ts.map +1 -1
  7. package/dist/clients/queries/tags.js +26 -4
  8. package/dist/clients/queries/tags.js.map +1 -1
  9. package/dist/clients/queries/topology.d.ts +8 -2
  10. package/dist/clients/queries/topology.d.ts.map +1 -1
  11. package/dist/clients/queries/topology.js +19 -2
  12. package/dist/clients/queries/topology.js.map +1 -1
  13. package/dist/clients/types.d.ts +4 -0
  14. package/dist/clients/types.d.ts.map +1 -1
  15. package/dist/errors/mcp-errors.d.ts +34 -0
  16. package/dist/errors/mcp-errors.d.ts.map +1 -1
  17. package/dist/errors/mcp-errors.js +71 -5
  18. package/dist/errors/mcp-errors.js.map +1 -1
  19. package/dist/tools/butlr-available-rooms.d.ts +5 -23
  20. package/dist/tools/butlr-available-rooms.d.ts.map +1 -1
  21. package/dist/tools/butlr-available-rooms.js +104 -51
  22. package/dist/tools/butlr-available-rooms.js.map +1 -1
  23. package/dist/tools/butlr-fetch-entity-details.d.ts +4 -0
  24. package/dist/tools/butlr-fetch-entity-details.d.ts.map +1 -1
  25. package/dist/tools/butlr-fetch-entity-details.js +31 -1
  26. package/dist/tools/butlr-fetch-entity-details.js.map +1 -1
  27. package/dist/tools/butlr-get-asset-details.d.ts.map +1 -1
  28. package/dist/tools/butlr-get-asset-details.js +29 -4
  29. package/dist/tools/butlr-get-asset-details.js.map +1 -1
  30. package/dist/tools/butlr-get-current-occupancy.d.ts.map +1 -1
  31. package/dist/tools/butlr-get-current-occupancy.js +15 -4
  32. package/dist/tools/butlr-get-current-occupancy.js.map +1 -1
  33. package/dist/tools/butlr-get-occupancy-timeseries.d.ts.map +1 -1
  34. package/dist/tools/butlr-get-occupancy-timeseries.js +16 -5
  35. package/dist/tools/butlr-get-occupancy-timeseries.js.map +1 -1
  36. package/dist/tools/butlr-list-tags.d.ts +28 -10
  37. package/dist/tools/butlr-list-tags.d.ts.map +1 -1
  38. package/dist/tools/butlr-list-tags.js +81 -24
  39. package/dist/tools/butlr-list-tags.js.map +1 -1
  40. package/dist/tools/butlr-list-topology.d.ts +7 -1
  41. package/dist/tools/butlr-list-topology.d.ts.map +1 -1
  42. package/dist/tools/butlr-list-topology.js +845 -35
  43. package/dist/tools/butlr-list-topology.js.map +1 -1
  44. package/dist/tools/butlr-search-assets.d.ts.map +1 -1
  45. package/dist/tools/butlr-search-assets.js +7 -1
  46. package/dist/tools/butlr-search-assets.js.map +1 -1
  47. package/dist/tools/butlr-space-busyness.d.ts.map +1 -1
  48. package/dist/tools/butlr-space-busyness.js +22 -4
  49. package/dist/tools/butlr-space-busyness.js.map +1 -1
  50. package/dist/tools/butlr-traffic-flow.d.ts.map +1 -1
  51. package/dist/tools/butlr-traffic-flow.js +9 -3
  52. package/dist/tools/butlr-traffic-flow.js.map +1 -1
  53. package/dist/types/responses.d.ts +123 -5
  54. package/dist/types/responses.d.ts.map +1 -1
  55. package/dist/utils/field-validator.d.ts +6 -0
  56. package/dist/utils/field-validator.d.ts.map +1 -1
  57. package/dist/utils/field-validator.js +5 -1
  58. package/dist/utils/field-validator.js.map +1 -1
  59. package/dist/utils/occupancy-helpers.d.ts +15 -2
  60. package/dist/utils/occupancy-helpers.d.ts.map +1 -1
  61. package/dist/utils/occupancy-helpers.js +116 -19
  62. package/dist/utils/occupancy-helpers.js.map +1 -1
  63. package/dist/utils/tag-resolver.d.ts +99 -0
  64. package/dist/utils/tag-resolver.d.ts.map +1 -0
  65. package/dist/utils/tag-resolver.js +108 -0
  66. package/dist/utils/tag-resolver.js.map +1 -0
  67. package/package.json +1 -1
@@ -32,8 +32,9 @@ function normalizeFieldName(field) {
32
32
  /**
33
33
  * Valid fields for each entity type
34
34
  * Based on GraphQL schema (mix of snake_case and camelCase)
35
+ * Exported for unit testing (FIELD_SELECTIONS sync invariant)
35
36
  */
36
- const VALID_FIELDS = {
37
+ export const VALID_FIELDS = {
37
38
  site: ["id", "name", "timezone", "siteNumber", "customID", "org_id", "buildings"],
38
39
  building: [
39
40
  "id",
@@ -62,6 +63,7 @@ const VALID_FIELDS = {
62
63
  "sensors",
63
64
  "hives",
64
65
  "building",
66
+ "tags",
65
67
  ],
66
68
  room: [
67
69
  "id",
@@ -76,6 +78,7 @@ const VALID_FIELDS = {
76
78
  "note",
77
79
  "sensors",
78
80
  "floor",
81
+ "tags",
79
82
  ],
80
83
  zone: [
81
84
  "id",
@@ -89,6 +92,7 @@ const VALID_FIELDS = {
89
92
  "rotation",
90
93
  "note",
91
94
  "sensors",
95
+ "tags",
92
96
  ],
93
97
  sensor: [
94
98
  "id",
@@ -1 +1 @@
1
- {"version":3,"file":"field-validator.js","sourceRoot":"","sources":["../../src/utils/field-validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"field-validator.js","sourceRoot":"","sources":["../../src/utils/field-validator.ts"],"names":[],"mappings":"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"}
@@ -55,11 +55,24 @@ export declare function getPresenceCoverageNote(assetType: "floor" | "room" | "z
55
55
  * Build coverage note for traffic measurement.
56
56
  */
57
57
  export declare function getTrafficCoverageNote(assetType: "floor" | "room" | "zone", sensorCount: number): string;
58
+ /**
59
+ * Caller context threaded through recommendation building. `assetType` decides
60
+ * structural applicability (zones don't support traffic); `windowLabel` is a
61
+ * human-readable description of the query window so failure copy doesn't claim
62
+ * a window the caller never queried (the current-occupancy tool uses a fixed
63
+ * 5-minute snapshot; the timeseries tool uses a caller-supplied range).
64
+ */
65
+ export interface RecommendationContext {
66
+ assetType: "floor" | "room" | "zone";
67
+ windowLabel: string;
68
+ }
58
69
  /**
59
70
  * Build measurement recommendation based on data availability AND query success.
60
71
  *
61
72
  * Unlike the previous implementation, this checks whether data was actually retrieved,
62
- * not just whether sensors exist.
73
+ * not just whether sensors exist. When both measurement types fail, the reason
74
+ * distinguishes three sub-cases (no sensors / sensors-but-quiet / call errored)
75
+ * so downstream LLMs don't conflate "uninstrumented space" with "no recent reads."
63
76
  */
64
- export declare function buildRecommendation(presence: BaseMeasurementData, traffic: BaseMeasurementData, presenceHasData: boolean, trafficHasData: boolean): MeasurementRecommendation;
77
+ export declare function buildRecommendation(presence: BaseMeasurementData, traffic: BaseMeasurementData, presenceHasData: boolean, trafficHasData: boolean, ctx: RecommendationContext): MeasurementRecommendation;
65
78
  //# sourceMappingURL=occupancy-helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"occupancy-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/occupancy-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAM5F;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,eAAe,CAAC,CA0BxE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACrC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,YAAY,CAmEvF;AA4BD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CASnF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAOzE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,EACpC,WAAW,EAAE,MAAM,GAClB,MAAM,CASR;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,EACpC,WAAW,EAAE,MAAM,GAClB,MAAM,CASR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,mBAAmB,EAC5B,eAAe,EAAE,OAAO,EACxB,cAAc,EAAE,OAAO,GACtB,yBAAyB,CA6B3B"}
1
+ {"version":3,"file":"occupancy-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/occupancy-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAQ,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAM5F;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,eAAe,CAAC,CA4BxE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACrC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,YAAY,CA8EvF;AAwCD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CASnF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAOzE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,EACpC,WAAW,EAAE,MAAM,GAClB,MAAM,CASR;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,EACpC,WAAW,EAAE,MAAM,GAClB,MAAM,CASR;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,mBAAmB,EAC5B,eAAe,EAAE,OAAO,EACxB,cAAc,EAAE,OAAO,EACvB,GAAG,EAAE,qBAAqB,GACzB,yBAAyB,CA4B3B"}
@@ -8,7 +8,7 @@
8
8
  import { apolloClient } from "../clients/graphql-client.js";
9
9
  import { GET_ALL_SENSORS, GET_FULL_TOPOLOGY } from "../clients/queries/topology.js";
10
10
  import { detectAssetType } from "./asset-helpers.js";
11
- import { isProductionSensor } from "./graphql-helpers.js";
11
+ import { isProductionSensor, throwIfGraphQLErrors } from "./graphql-helpers.js";
12
12
  import { getTimezoneForAsset, buildTimezoneMetadata } from "./timezone-helpers.js";
13
13
  import { rethrowIfGraphQLError } from "./graphql-helpers.js";
14
14
  /**
@@ -28,6 +28,8 @@ export async function fetchTopologyAndSensors() {
28
28
  fetchPolicy: "network-only",
29
29
  }),
30
30
  ]);
31
+ throwIfGraphQLErrors(topoResult);
32
+ throwIfGraphQLErrors(sensorsResult);
31
33
  }
32
34
  catch (error) {
33
35
  rethrowIfGraphQLError(error);
@@ -58,28 +60,41 @@ export function resolveAssetContext(assetId, ctx) {
58
60
  : undefined;
59
61
  // Resolve asset name
60
62
  const assetName = findAssetName(assetId, typedAssetType, ctx.floors);
61
- // Filter sensors for this asset
62
- const assetSensors = ctx.productionSensors.filter((s) => {
63
- const sensorFloorId = s.floor_id || s.floorID;
64
- const sensorRoomId = s.room_id || s.roomID;
65
- switch (typedAssetType) {
66
- case "floor":
67
- return sensorFloorId === assetId;
68
- case "room":
69
- return sensorRoomId === assetId;
70
- case "zone":
71
- return false; // Zones don't have direct sensor assignments
72
- }
73
- });
63
+ // Filter sensors for this asset. Rooms get sensors via the flat
64
+ // productionSensors list (joined by sensor.room_id). Zones get them
65
+ // via the topology's floor.zones[i].sensors relation — they have
66
+ // their own directly-attributed sensors, NOT inherited from any
67
+ // notional "parent room" (zones and rooms are siblings under a
68
+ // floor; the legacy zone.room_id field is decorative).
69
+ let assetSensors;
70
+ if (typedAssetType === "zone") {
71
+ const zone = findZone(assetId, ctx.floors);
72
+ assetSensors = (zone?.sensors ?? []).filter(isProductionSensor);
73
+ }
74
+ else {
75
+ assetSensors = ctx.productionSensors.filter((s) => {
76
+ const sensorFloorId = s.floor_id || s.floorID;
77
+ const sensorRoomId = s.room_id || s.roomID;
78
+ return typedAssetType === "floor" ? sensorFloorId === assetId : sensorRoomId === assetId;
79
+ });
80
+ }
74
81
  // Partition sensors by mode
75
82
  const presenceSensors = assetSensors.filter((s) => s.mode === "presence");
76
83
  let trafficSensors;
77
84
  switch (typedAssetType) {
78
85
  case "floor":
86
+ // Floor-level traffic comes from the building/floor entrances.
79
87
  trafficSensors = assetSensors.filter((s) => s.mode === "traffic" && s.is_entrance === true);
80
88
  break;
81
89
  case "room":
82
- trafficSensors = assetSensors.filter((s) => s.mode === "traffic" && s.is_entrance === false);
90
+ // Room-level traffic includes every traffic-mode sensor bound to the
91
+ // room. `is_entrance` is a semantic flag indicating the sensor sits at
92
+ // a building/floor entrance — it is not a routing flag. The Reporting
93
+ // API aggregates by `room_id` regardless, so filtering on
94
+ // `is_entrance === false` here would silently drop counts for rooms
95
+ // whose sensors are all entrances (e.g. a café occupying the floor's
96
+ // entry area).
97
+ trafficSensors = assetSensors.filter((s) => s.mode === "traffic");
83
98
  break;
84
99
  default:
85
100
  trafficSensors = [];
@@ -118,6 +133,18 @@ function findAssetName(assetId, assetType, floors) {
118
133
  return undefined;
119
134
  }
120
135
  }
136
+ /**
137
+ * Find a zone object in the topology by id. Returns undefined if the zone
138
+ * isn't present (e.g. stale id or topology that didn't include zones).
139
+ */
140
+ function findZone(zoneId, floors) {
141
+ for (const floor of floors) {
142
+ const zone = floor.zones?.find((z) => z.id === zoneId);
143
+ if (zone)
144
+ return zone;
145
+ }
146
+ return undefined;
147
+ }
121
148
  /**
122
149
  * Get the measurement name for presence data based on asset type.
123
150
  */
@@ -174,9 +201,11 @@ export function getTrafficCoverageNote(assetType, sensorCount) {
174
201
  * Build measurement recommendation based on data availability AND query success.
175
202
  *
176
203
  * Unlike the previous implementation, this checks whether data was actually retrieved,
177
- * not just whether sensors exist.
204
+ * not just whether sensors exist. When both measurement types fail, the reason
205
+ * distinguishes three sub-cases (no sensors / sensors-but-quiet / call errored)
206
+ * so downstream LLMs don't conflate "uninstrumented space" with "no recent reads."
178
207
  */
179
- export function buildRecommendation(presence, traffic, presenceHasData, trafficHasData) {
208
+ export function buildRecommendation(presence, traffic, presenceHasData, trafficHasData, ctx) {
180
209
  const presenceSucceeded = presence.available && presenceHasData && !presence.warning;
181
210
  const trafficSucceeded = traffic.available && trafficHasData && !traffic.warning;
182
211
  if (presenceSucceeded && trafficSucceeded) {
@@ -197,10 +226,78 @@ export function buildRecommendation(presence, traffic, presenceHasData, trafficH
197
226
  recommendation_reason: "Traffic available (entry/exit counts).",
198
227
  };
199
228
  }
200
- const failureReason = presence.warning || traffic.warning || "No occupancy data available.";
201
229
  return {
202
230
  recommended_measurement: "none",
203
- recommendation_reason: failureReason,
231
+ recommendation_reason: buildFailureReason(presence, traffic, ctx),
204
232
  };
205
233
  }
234
+ /**
235
+ * Number of sensors configured for a measurement, regardless of which field
236
+ * the caller populated: floors report traffic via `entrance_sensor_count`;
237
+ * rooms and zones use `sensor_count`.
238
+ */
239
+ function configuredSensorCount(data) {
240
+ return data.sensor_count ?? data.entrance_sensor_count ?? 0;
241
+ }
242
+ /**
243
+ * Compose the recommendation_reason when neither presence nor traffic yielded a
244
+ * usable reading. Prefers the more actionable of the two measurement types,
245
+ * giving the customer something concrete to do next (instrument the space,
246
+ * wait for the next read, check sensor health, or retry the call).
247
+ *
248
+ * Avoids the literal phrase "no occupancy data" — downstream LLMs were quoting
249
+ * it verbatim and turning "quiet but instrumented" into "the space is
250
+ * unmonitored," which is the opposite of true.
251
+ */
252
+ function buildFailureReason(presence, traffic, ctx) {
253
+ const presenceReason = describeMeasurementFailure("presence", presence, ctx);
254
+ const trafficReason = describeMeasurementFailure("traffic", traffic, ctx);
255
+ // Prefer the measurement type carrying real signal: one with sensors
256
+ // configured, or whose call errored mid-flight. Without this, a floor with
257
+ // working entrance sensors but zero presence sensors would surface
258
+ // presence's "no sensors configured" copy and read as uninstrumented.
259
+ // Presence wins ties — it's the more informative signal when both
260
+ // measurement types are instrumented but quiet.
261
+ const presenceHasSignal = configuredSensorCount(presence) > 0 || Boolean(presence.warning);
262
+ const trafficHasSignal = configuredSensorCount(traffic) > 0 || Boolean(traffic.warning);
263
+ if (presenceReason && presenceHasSignal)
264
+ return presenceReason;
265
+ if (trafficReason && trafficHasSignal)
266
+ return trafficReason;
267
+ // Neither measurement type has sensors: the space is uninstrumented.
268
+ // Presence first — it applies to every asset type.
269
+ if (presenceReason)
270
+ return presenceReason;
271
+ if (trafficReason)
272
+ return trafficReason;
273
+ // Defensive fallback for totality only. Unreachable today: presence is
274
+ // applicable to every asset type, so `presenceReason` is always set.
275
+ return "No recent occupancy reads. Check butlr_hardware_snapshot for sensor health.";
276
+ }
277
+ /**
278
+ * Returns a customer-facing explanation of why a measurement type produced no
279
+ * usable reading, or `undefined` if the measurement type is structurally
280
+ * inapplicable to this asset (traffic on a zone). Only called for measurements
281
+ * that yielded no usable reading, so "sensors configured" below means
282
+ * configured-but-quiet.
283
+ */
284
+ function describeMeasurementFailure(kind, data, ctx) {
285
+ // Traffic on a zone is the only structurally-N/A combination. The caller's
286
+ // coverage_note already explains why; surfacing it here would be redundant.
287
+ if (kind === "traffic" && ctx.assetType === "zone")
288
+ return undefined;
289
+ // The call errored. Surface the warning verbatim so the customer (and any
290
+ // LLM reading the response) gets the actual failure mode.
291
+ if (data.warning) {
292
+ return `Tried to retrieve ${kind} occupancy but the request failed: ${data.warning}. Retry with a smaller asset set, or check butlr_hardware_snapshot for sensor health.`;
293
+ }
294
+ // Sensors are configured but the Reporting API had no reads in the query
295
+ // window. Honest: we don't know if the space is empty or if the sensors
296
+ // are stuck — point at hardware_snapshot for the health-check answer.
297
+ if (configuredSensorCount(data) > 0) {
298
+ return `Sensor(s) configured but no ${kind} reads in ${ctx.windowLabel}. The asset may currently be empty, or the sensor(s) may need a health check via butlr_hardware_snapshot.`;
299
+ }
300
+ // No sensors at all. The space is uninstrumented for this measurement type.
301
+ return `No ${kind} sensors configured for this asset. Contact facilities to instrument it before requesting ${kind} occupancy.`;
302
+ }
206
303
  //# sourceMappingURL=occupancy-helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"occupancy-helpers.js","sourceRoot":"","sources":["../../src/utils/occupancy-helpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"occupancy-helpers.js","sourceRoot":"","sources":["../../src/utils/occupancy-helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { type TagId, type TagMatch, type TagName, type TaggedEntityRef } from "../clients/queries/tags.js";
2
+ /**
3
+ * Pure tag-name → tag-row resolution shared by `butlr_available_rooms` and
4
+ * `butlr_list_topology`. Pure-function over an already-fetched row list so
5
+ * each caller can fetch the minimum tag shape it needs without the helper
6
+ * having to know about Apollo or GraphQL queries.
7
+ *
8
+ * The three-arm return contract (`ok` / `no_match` / `unsatisfiable`) is
9
+ * documented on `ResolveTagNamesResult` below — callers MUST switch on
10
+ * `kind` before touching the resolved arms.
11
+ */
12
+ export interface ResolveTagNamesInput<Row extends {
13
+ id: string;
14
+ name: string;
15
+ }> {
16
+ /** All tag rows fetched from the API (any shape that has at least id+name). */
17
+ allTags: Row[];
18
+ /** Names supplied by the caller (case-insensitive match against `allTags[].name`). */
19
+ requestedNames: string[];
20
+ /** Multi-tag semantics — only affects the `unsatisfiable` discriminant for unknown tags. */
21
+ match: TagMatch;
22
+ }
23
+ /**
24
+ * Discriminated union over the three terminal states.
25
+ *
26
+ * Callers branch on `kind` first; the type structurally prevents reading
27
+ * `resolvedRows` on a non-`ok` branch, where the resolved subset must NOT
28
+ * be used (it would silently broaden a `match='all'` query to `match='any'`
29
+ * semantics, or hide an all-unknown input behind a misleading "partial
30
+ * resolution" path).
31
+ *
32
+ * - `ok` — at least one requested name resolved; safe to continue with the
33
+ * subset. Under `match='all'` this implies every name resolved.
34
+ * - `no_match` — every requested name was unknown. Distinct from
35
+ * `unsatisfiable` because the right diagnostic is "no matching tags
36
+ * found" rather than "cannot satisfy AND" (with one input there's no AND
37
+ * to satisfy).
38
+ * - `unsatisfiable` — `match='all'` with at least one resolved AND at
39
+ * least one unknown. Asking for the AND is impossible; the resolved
40
+ * subset is intentionally hidden so a caller can't accidentally fall
41
+ * back to `match='any'` semantics.
42
+ */
43
+ export type ResolveTagNamesResult<Row extends {
44
+ id: string;
45
+ name: string;
46
+ }> = {
47
+ kind: "ok";
48
+ /** Tag rows whose names matched — preserves the input row shape. */
49
+ resolvedRows: Row[];
50
+ /** Resolved tag IDs in the same order as `resolvedRows`. */
51
+ resolvedIds: TagId[];
52
+ /** Names from the input that did not match any tag. */
53
+ unknownNames: TagName[];
54
+ /**
55
+ * Number of malformed rows skipped by the defensive guard (missing or
56
+ * empty `id` / `name`, or canonical-name duplicates). Non-zero values
57
+ * indicate an upstream contract violation that callers should surface
58
+ * as a `malformed_tag_rows` diagnostic — silent filtering would
59
+ * otherwise hide the breakage.
60
+ */
61
+ droppedRowCount: number;
62
+ /**
63
+ * Up to ~5 representative names from the dropped rows (where the
64
+ * name was usable). Primarily useful for the duplicate-canonical
65
+ * case so operators can see which names collided.
66
+ */
67
+ droppedSampleNames: string[];
68
+ } | {
69
+ kind: "no_match";
70
+ /** Every requested name, in input order. */
71
+ unknownNames: TagName[];
72
+ /** Same semantics as the `ok` variant — surface as a diagnostic if non-zero. */
73
+ droppedRowCount: number;
74
+ droppedSampleNames: string[];
75
+ } | {
76
+ kind: "unsatisfiable";
77
+ /** The unknown subset that prevents satisfying `match='all'`. */
78
+ unknownNames: TagName[];
79
+ /** The resolved subset, hidden from callers so they can't broaden semantics. */
80
+ partialResolvedCount: number;
81
+ /** Same semantics as the `ok` variant — surface as a diagnostic if non-zero. */
82
+ droppedRowCount: number;
83
+ droppedSampleNames: string[];
84
+ };
85
+ export declare function resolveTagNames<Row extends {
86
+ id: string;
87
+ name: string;
88
+ }>(input: ResolveTagNamesInput<Row>): ResolveTagNamesResult<Row>;
89
+ /**
90
+ * Filter a tagged-entity ref list to only entries with a usable `id`, and
91
+ * drop the optional `name` when upstream omits it. Used by both
92
+ * `butlr_list_tags` (`applied_to_entities` projection) and
93
+ * `butlr_list_topology` (`collectDirectTaggedIds` + `collectMatchAwareClosure`) so the
94
+ * "what counts as a non-dangling ref" predicate lives in one place — they
95
+ * cannot drift apart, and the count + entity arrays produced from the same
96
+ * filtered list are guaranteed to agree.
97
+ */
98
+ export declare function projectValidRefs(refs: ReadonlyArray<TaggedEntityRef> | null | undefined): TaggedEntityRef[];
99
+ //# sourceMappingURL=tag-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/tag-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,QAAQ,EACb,KAAK,OAAO,EACZ,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;GASG;AAEH,MAAM,WAAW,oBAAoB,CAAC,GAAG,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IAC5E,+EAA+E;IAC/E,OAAO,EAAE,GAAG,EAAE,CAAC;IACf,sFAAsF;IACtF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,4FAA4F;IAC5F,KAAK,EAAE,QAAQ,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,qBAAqB,CAAC,GAAG,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,IACtE;IACE,IAAI,EAAE,IAAI,CAAC;IACX,oEAAoE;IACpE,YAAY,EAAE,GAAG,EAAE,CAAC;IACpB,4DAA4D;IAC5D,WAAW,EAAE,KAAK,EAAE,CAAC;IACrB,uDAAuD;IACvD,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,4CAA4C;IAC5C,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,gFAAgF;IAChF,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B,GACD;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,iEAAiE;IACjE,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,gFAAgF;IAChF,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gFAAgF;IAChF,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEN,wBAAgB,eAAe,CAAC,GAAG,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACtE,KAAK,EAAE,oBAAoB,CAAC,GAAG,CAAC,GAC/B,qBAAqB,CAAC,GAAG,CAAC,CA8F5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,IAAI,GAAG,SAAS,GACtD,eAAe,EAAE,CAOnB"}
@@ -0,0 +1,108 @@
1
+ import { asTagId, asTagName, } from "../clients/queries/tags.js";
2
+ export function resolveTagNames(input) {
3
+ const { allTags, requestedNames, match } = input;
4
+ // Defensively skip rows whose `name` or `id` is not a usable string. The
5
+ // type contract says both are required, but a missing field on a partial
6
+ // GraphQL response would otherwise throw on `.toLowerCase()` (name) or
7
+ // surface a null branded TagId downstream (id), crashing every tag-using
8
+ // tool. Empty strings are rejected too — they can never match a
9
+ // Zod-validated request and would sit as dead weight in the lookup.
10
+ // The dropped count is returned to the caller so the upstream contract
11
+ // violation is visible (rather than silently masking it as "unknown tag").
12
+ const lookup = new Map();
13
+ let droppedRowCount = 0;
14
+ const droppedSampleNames = [];
15
+ const SAMPLE_LIMIT = 5;
16
+ const noteDroppedName = (name) => {
17
+ if (typeof name === "string" &&
18
+ name.trim().length > 0 &&
19
+ droppedSampleNames.length < SAMPLE_LIMIT) {
20
+ droppedSampleNames.push(name);
21
+ }
22
+ };
23
+ for (const t of allTags) {
24
+ // Whitespace-only counts as "missing" — asTagId / asTagName would
25
+ // reject it later, but the boundary check is the right place for
26
+ // the user-facing diagnostic to fire from.
27
+ if (typeof t.name !== "string" || t.name.trim().length === 0) {
28
+ droppedRowCount++;
29
+ // No usable name to sample — diagnostic carries count only.
30
+ continue;
31
+ }
32
+ if (typeof t.id !== "string" || t.id.trim().length === 0) {
33
+ droppedRowCount++;
34
+ noteDroppedName(t.name);
35
+ continue;
36
+ }
37
+ const key = t.name.toLowerCase();
38
+ // First-write-wins on duplicate canonical names. If upstream returns
39
+ // two rows with case-insensitively equal names (e.g. "Huddle" and
40
+ // "huddle"), last-write would non-deterministically depend on
41
+ // upstream order and silently mask the upstream-contract violation.
42
+ // Treating the dup as a malformed row keeps resolution deterministic
43
+ // and surfaces the issue via the same droppedRowCount → malformed_tag_rows
44
+ // diagnostic that catches null/empty rows.
45
+ if (lookup.has(key)) {
46
+ droppedRowCount++;
47
+ noteDroppedName(t.name);
48
+ continue;
49
+ }
50
+ lookup.set(key, t);
51
+ }
52
+ const resolvedRows = [];
53
+ const resolvedIds = [];
54
+ const unknownNames = [];
55
+ for (const rawName of requestedNames) {
56
+ const name = asTagName(rawName);
57
+ const row = lookup.get(name.toLowerCase());
58
+ if (row) {
59
+ resolvedRows.push(row);
60
+ resolvedIds.push(asTagId(row.id));
61
+ }
62
+ else {
63
+ unknownNames.push(name);
64
+ }
65
+ }
66
+ if (resolvedRows.length === 0 && unknownNames.length > 0) {
67
+ // At least one name was requested and nothing matched — distinct from
68
+ // `unsatisfiable` so callers can emit "no matching tags found" rather
69
+ // than the misleading "cannot satisfy AND" (when only one input was
70
+ // sent there's no AND to fail). Empty `requestedNames` falls through
71
+ // to the `ok` branch as a trivially-empty resolution.
72
+ return { kind: "no_match", unknownNames, droppedRowCount, droppedSampleNames };
73
+ }
74
+ if (match === "all" && unknownNames.length > 0) {
75
+ return {
76
+ kind: "unsatisfiable",
77
+ unknownNames,
78
+ partialResolvedCount: resolvedRows.length,
79
+ droppedRowCount,
80
+ droppedSampleNames,
81
+ };
82
+ }
83
+ return {
84
+ kind: "ok",
85
+ resolvedRows,
86
+ resolvedIds,
87
+ unknownNames,
88
+ droppedRowCount,
89
+ droppedSampleNames,
90
+ };
91
+ }
92
+ /**
93
+ * Filter a tagged-entity ref list to only entries with a usable `id`, and
94
+ * drop the optional `name` when upstream omits it. Used by both
95
+ * `butlr_list_tags` (`applied_to_entities` projection) and
96
+ * `butlr_list_topology` (`collectDirectTaggedIds` + `collectMatchAwareClosure`) so the
97
+ * "what counts as a non-dangling ref" predicate lives in one place — they
98
+ * cannot drift apart, and the count + entity arrays produced from the same
99
+ * filtered list are guaranteed to agree.
100
+ */
101
+ export function projectValidRefs(refs) {
102
+ if (!refs)
103
+ return [];
104
+ return refs.flatMap((ref) => typeof ref.id === "string" && ref.id.trim().length > 0
105
+ ? [typeof ref.name === "string" ? { id: ref.id, name: ref.name } : { id: ref.id }]
106
+ : []);
107
+ }
108
+ //# sourceMappingURL=tag-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-resolver.js","sourceRoot":"","sources":["../../src/utils/tag-resolver.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@butlr/butlr-mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.5.0",
4
4
  "description": "Model Context Protocol server providing secure, read-only access to Butlr occupancy and asset data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",