@letpeoplework/lighthouse-mcp-core 1.7.1 → 1.8.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.
package/dist/index.cjs CHANGED
@@ -1,1669 +1,1939 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- createMcpCoreRuntime: () => createMcpCoreRuntime,
24
- getMcpCorePackageContract: () => getMcpCorePackageContract,
25
- registerMcpTools: () => registerMcpTools
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _letpeoplework_lighthouse_client = require("@letpeoplework/lighthouse-client");
3
+ let _modelcontextprotocol_sdk_types_js = require("@modelcontextprotocol/sdk/types.js");
4
+ let zod = require("zod");
5
+ let _toon_format_toon = require("@toon-format/toon");
6
+ //#region src/toolResult.ts
7
+ const encodePayload = (value) => {
8
+ try {
9
+ return (0, _toon_format_toon.encode)(value);
10
+ } catch {
11
+ return JSON.stringify(value);
12
+ }
13
+ };
14
+ const getSuccessToolResult = (text) => ({
15
+ isError: false,
16
+ content: [{
17
+ type: "text",
18
+ text
19
+ }]
26
20
  });
27
- module.exports = __toCommonJS(index_exports);
28
- var import_lighthouse_client = require("@letpeoplework/lighthouse-client");
29
- var import_toon = require("@toon-format/toon");
30
- var import_zod = require("zod");
31
- var getMcpCorePackageContract = () => ({
32
- name: "@letpeoplework/lighthouse-mcp-core",
33
- dependsOn: "@letpeoplework/lighthouse-client",
34
- transports: ["stdio", "streamable-http"]
21
+ const isFactsObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22
+ /**
23
+ * The facts under their label, with the answer stated as the web does beside them. Without a summary the
24
+ * facts go out exactly as they always have, so an assistant reading them never sees a different shape.
25
+ * An object takes the summary as a field. A list, a single value, or an object that already has a
26
+ * `summary` of its own cannot take one without changing its facts, so its summary follows in a second
27
+ * block.
28
+ */
29
+ const withSummary = (label, facts, summary) => {
30
+ if (summary !== null && isFactsObject(facts) && !("summary" in facts)) return getSuccessToolResult(`${label}: ${encodePayload({
31
+ summary,
32
+ ...facts
33
+ })}`);
34
+ return withSummaryBlock(`${label}: ${encodePayload(facts)}`, summary);
35
+ };
36
+ /** A text block exactly as it is, with the summary in a second block after it; without one, the text alone. */
37
+ const withSummaryBlock = (text, summary) => summary === null ? getSuccessToolResult(text) : {
38
+ isError: false,
39
+ content: [{
40
+ type: "text",
41
+ text
42
+ }, {
43
+ type: "text",
44
+ text: `summary: ${summary}`
45
+ }]
46
+ };
47
+ const getErrorToolResult = (text) => ({
48
+ isError: true,
49
+ content: [{
50
+ type: "text",
51
+ text
52
+ }]
35
53
  });
36
- var emptyInputSchema = {
37
- type: "object",
38
- properties: {},
39
- additionalProperties: false
40
- };
41
- var idInputSchema = {
42
- type: "object",
43
- properties: {
44
- id: {
45
- type: "integer",
46
- description: "Unique numeric identifier."
47
- }
48
- },
49
- required: ["id"],
50
- additionalProperties: false
51
- };
52
- var dateRangeProperties = {
53
- startDate: {
54
- type: "string",
55
- description: "Inclusive start date in ISO format (YYYY-MM-DD)."
56
- },
57
- endDate: {
58
- type: "string",
59
- description: "Inclusive end date in ISO format (YYYY-MM-DD)."
60
- }
61
- };
62
- var throughputFilterViewProperty = {
63
- view: {
64
- type: "string",
65
- enum: ["raw", "filtered"],
66
- description: `Forecast-filter view (Lighthouse v26.5.24.10+). "filtered" applies the team's exclusion rule to throughput. Omit or "raw" returns unfiltered data.`
67
- }
68
- };
69
- var percentilesOverTimeProperties = {
70
- metricType: {
71
- type: "string",
72
- enum: ["CycleTime", "WorkItemAge"],
73
- description: 'Percentile family to read. Defaults to "CycleTime" when omitted.'
74
- },
75
- horizon: {
76
- type: "number",
77
- description: "Cycle-time horizon in days (30, 60 or 90). Ignored for WorkItemAge, which is always as-of-today and has no horizon dimension."
78
- }
79
- };
80
- var processBehaviorOverTimeProperties = {
81
- metricType: {
82
- type: "string",
83
- enum: [
84
- "Throughput",
85
- "WorkItemAge",
86
- "Wip",
87
- "CycleTime",
88
- "Arrivals",
89
- "FeatureSize"
90
- ],
91
- description: 'Process-behaviour family to read. Defaults to "Throughput" when omitted. "FeatureSize" is portfolio-only \u2014 a team never records it and returns an empty series.'
92
- }
93
- };
94
- var cumulativeStateProperty = {
95
- state: {
96
- type: "string",
97
- description: "Workflow state name to drill into (must match a bar's state from the cumulativeStateTime response)."
98
- }
99
- };
100
- var itemIdsProperty = {
101
- itemIds: {
102
- type: "array",
103
- items: { type: "integer" },
104
- description: "Optional work-item IDs to narrow the computation to a selected subset; omit for all in-scope items."
105
- }
106
- };
107
- var forecastFilterOverrideProperty = {
108
- applyFilterOverride: {
109
- type: "boolean",
110
- description: "Override the team's forecast-filter setting (Lighthouse v26.5.24.10+). true = apply the team filter; false = skip the filter (raw); omit = respect the team setting."
111
- }
112
- };
113
- var recurringBlackoutRuleProperties = {
114
- weekdays: {
115
- type: "array",
116
- items: {
117
- type: "string",
118
- enum: [
119
- "Sunday",
120
- "Monday",
121
- "Tuesday",
122
- "Wednesday",
123
- "Thursday",
124
- "Friday",
125
- "Saturday"
126
- ]
127
- },
128
- description: "Weekdays the rule applies to."
129
- },
130
- intervalWeeks: {
131
- type: "integer",
132
- description: "Repeat every N weeks (1 = every week)."
133
- },
134
- start: {
135
- type: "string",
136
- description: "Rule start date in ISO format (YYYY-MM-DD)."
137
- },
138
- end: {
139
- type: ["string", "null"],
140
- description: "Optional end date in ISO format (YYYY-MM-DD), or null for an open end."
141
- },
142
- description: {
143
- type: "string",
144
- description: "Human-readable description of the rule."
145
- }
146
- };
147
- var toolDefinitions = [
148
- {
149
- name: "lighthouse_health_check",
150
- description: "Check connectivity to Lighthouse and return whether the configured endpoint is reachable.",
151
- inputSchema: emptyInputSchema
152
- },
153
- {
154
- name: "lighthouse_version_get",
155
- description: "Retrieve the Lighthouse server version from the version endpoint.",
156
- inputSchema: emptyInputSchema
157
- },
158
- {
159
- name: "lighthouse_worktracking_list",
160
- description: "List configured work-tracking system connections in Lighthouse.",
161
- inputSchema: emptyInputSchema
162
- },
163
- {
164
- name: "lighthouse_worktracking_get",
165
- description: "Get a single work-tracking system connection by ID.",
166
- inputSchema: idInputSchema
167
- },
168
- {
169
- name: "lighthouse_team_list",
170
- description: "List all teams available in Lighthouse.",
171
- inputSchema: emptyInputSchema
172
- },
173
- {
174
- name: "lighthouse_team_get",
175
- description: "Get full details for one team by ID.",
176
- inputSchema: idInputSchema
177
- },
178
- {
179
- name: "lighthouse_team_refresh",
180
- description: "Trigger data refresh for a team by ID.",
181
- inputSchema: idInputSchema
182
- },
183
- {
184
- name: "lighthouse_portfolio_list",
185
- description: "List all portfolios in Lighthouse.",
186
- inputSchema: emptyInputSchema
187
- },
188
- {
189
- name: "lighthouse_portfolio_get",
190
- description: "Get full details for one portfolio by ID.",
191
- inputSchema: idInputSchema
192
- },
193
- {
194
- name: "lighthouse_portfolio_refresh",
195
- description: "Trigger data refresh for a portfolio by ID.",
196
- inputSchema: idInputSchema
197
- },
198
- {
199
- name: "lighthouse_team_metrics_throughput",
200
- description: `Get throughput run-chart data for a team by ID, optionally filtered by start and end dates. Pass view="filtered" to apply the team's forecast-exclusion rule (Lighthouse v26.5.24.10+); omit or "raw" returns unfiltered data.`,
201
- inputSchema: {
202
- type: "object",
203
- properties: {
204
- ...idInputSchema.properties,
205
- ...dateRangeProperties,
206
- ...throughputFilterViewProperty
207
- },
208
- required: ["id"],
209
- additionalProperties: false
210
- }
211
- },
212
- {
213
- name: "lighthouse_team_metrics_cycleTimePercentiles",
214
- description: "Get cycle-time percentiles for a team by ID, optionally filtered by start and end dates. Pass definitionId to get the percentiles for a named cycle time (premium) instead of the default cycle time.",
215
- inputSchema: {
216
- type: "object",
217
- properties: {
218
- ...idInputSchema.properties,
219
- ...dateRangeProperties,
220
- definitionId: {
221
- type: "number",
222
- description: "Optional named cycle time definition ID; omit for the default cycle time."
223
- }
224
- },
225
- required: ["id"],
226
- additionalProperties: false
227
- }
228
- },
229
- {
230
- name: "lighthouse_team_metrics_workItemAgePercentiles",
231
- description: "Get work-item age percentiles for a team by ID, optionally filtered by start and end dates. Ages are measured as of the last day of the selected range, not as of today \u2014 a historical range reports how old the items were at the end of that period, so do not present the result as the team's current ages unless the range ends today.",
232
- inputSchema: {
233
- type: "object",
234
- properties: {
235
- ...idInputSchema.properties,
236
- ...dateRangeProperties
237
- },
238
- required: ["id"],
239
- additionalProperties: false
240
- }
241
- },
242
- {
243
- name: "lighthouse_portfolio_metrics_workItemAgePercentiles",
244
- description: "Get work-item age percentiles for a portfolio by ID, optionally filtered by start and end dates. Ages are measured as of the last day of the selected range, not as of today \u2014 a historical range reports how old the items were at the end of that period, so do not present the result as the portfolio's current ages unless the range ends today.",
245
- inputSchema: {
246
- type: "object",
247
- properties: {
248
- ...idInputSchema.properties,
249
- ...dateRangeProperties
250
- },
251
- required: ["id"],
252
- additionalProperties: false
253
- }
254
- },
255
- {
256
- name: "lighthouse_team_metrics_blockedCountHistory",
257
- description: "Get the blocked-items-over-time trend for a team by ID: how many work items were blocked on each captured day, optionally filtered by start and end dates. To see what is blocked right now and for how long, read the team's current WIP \u2014 each item carries isBlocked and, when blocked, a blockedSince timestamp.",
258
- inputSchema: {
259
- type: "object",
260
- properties: {
261
- ...idInputSchema.properties,
262
- ...dateRangeProperties
263
- },
264
- required: ["id"],
265
- additionalProperties: false
266
- }
267
- },
268
- {
269
- name: "lighthouse_portfolio_metrics_blockedCountHistory",
270
- description: "Get the blocked-items-over-time trend for a portfolio by ID: how many work items were blocked on each captured day, optionally filtered by start and end dates. To see what is blocked right now and for how long, read the portfolio's current WIP \u2014 each item carries isBlocked and, when blocked, a blockedSince timestamp.",
271
- inputSchema: {
272
- type: "object",
273
- properties: {
274
- ...idInputSchema.properties,
275
- ...dateRangeProperties
276
- },
277
- required: ["id"],
278
- additionalProperties: false
279
- }
280
- },
281
- {
282
- name: "lighthouse_team_metrics_percentilesOverTime",
283
- description: "Get the percentiles-over-time trend for a team by ID: the p50/p70/p85/p95 quartet recorded on each captured day, optionally filtered by start and end dates. By default Lighthouse only returns days it recorded, so a recently upgraded server returns an empty series until it has recorded some; where a System Admin has switched on filling in past days (a Preview), the read starts filling missing days in the background and a later call may return more. Use metricType to pick the family and horizon to pick the cycle-time window.",
284
- inputSchema: {
285
- type: "object",
286
- properties: {
287
- ...idInputSchema.properties,
288
- ...dateRangeProperties,
289
- ...percentilesOverTimeProperties
290
- },
291
- required: ["id"],
292
- additionalProperties: false
293
- }
294
- },
295
- {
296
- name: "lighthouse_portfolio_metrics_percentilesOverTime",
297
- description: "Get the percentiles-over-time trend for a portfolio by ID: the p50/p70/p85/p95 quartet recorded on each captured day, optionally filtered by start and end dates. By default Lighthouse only returns days it recorded, so a recently upgraded server returns an empty series until it has recorded some; where a System Admin has switched on filling in past days (a Preview), the read starts filling missing days in the background and a later call may return more. Use metricType to pick the family and horizon to pick the cycle-time window.",
298
- inputSchema: {
299
- type: "object",
300
- properties: {
301
- ...idInputSchema.properties,
302
- ...dateRangeProperties,
303
- ...percentilesOverTimeProperties
304
- },
305
- required: ["id"],
306
- additionalProperties: false
307
- }
308
- },
309
- {
310
- name: "lighthouse_team_metrics_processBehaviorOverTime",
311
- description: "Get the process-behaviour-limits-over-time trend for a team by ID: the upper limit, average and lower limit (UNPL/Average/LNPL) recorded on each captured day, optionally filtered by start and end dates. Days are recorded on refresh (and, where a System Admin has switched on filling in past days, filled in the background after a read), and days without a usable baseline are absent rather than zeroed \u2014 an empty series means nothing was recorded, never a process pinned at zero.",
312
- inputSchema: {
313
- type: "object",
314
- properties: {
315
- ...idInputSchema.properties,
316
- ...dateRangeProperties,
317
- ...processBehaviorOverTimeProperties
318
- },
319
- required: ["id"],
320
- additionalProperties: false
321
- }
322
- },
323
- {
324
- name: "lighthouse_portfolio_metrics_processBehaviorOverTime",
325
- description: "Get the process-behaviour-limits-over-time trend for a portfolio by ID: the upper limit, average and lower limit (UNPL/Average/LNPL) recorded on each captured day, optionally filtered by start and end dates. Days are recorded on refresh (and, where a System Admin has switched on filling in past days, filled in the background after a read), and days without a usable baseline are absent rather than zeroed \u2014 an empty series means nothing was recorded, never a process pinned at zero. FeatureSize is available here and not on teams.",
326
- inputSchema: {
327
- type: "object",
328
- properties: {
329
- ...idInputSchema.properties,
330
- ...dateRangeProperties,
331
- ...processBehaviorOverTimeProperties
332
- },
333
- required: ["id"],
334
- additionalProperties: false
335
- }
336
- },
337
- {
338
- name: "lighthouse_portfolio_metrics_throughput",
339
- description: "Get throughput run-chart data for a portfolio by ID, optionally filtered by start and end dates.",
340
- inputSchema: {
341
- type: "object",
342
- properties: {
343
- ...idInputSchema.properties,
344
- ...dateRangeProperties
345
- },
346
- required: ["id"],
347
- additionalProperties: false
348
- }
349
- },
350
- {
351
- name: "lighthouse_team_metrics_workItemAge",
352
- description: "Get per-item work item age over time for a team by ID. Returns daily snapshots with each in-progress item's age in days derived from its startedDate. Items without a startedDate are omitted.",
353
- inputSchema: {
354
- type: "object",
355
- properties: {
356
- ...idInputSchema.properties,
357
- ...dateRangeProperties
358
- },
359
- required: ["id"],
360
- additionalProperties: false
361
- }
362
- },
363
- {
364
- name: "lighthouse_team_metrics_totalWorkItemAge",
365
- description: "Get the total (summed) work item age over time for a team by ID. Returns daily totals of all in-progress item ages derived from startedDate. Items without a startedDate are not counted.",
366
- inputSchema: {
367
- type: "object",
368
- properties: {
369
- ...idInputSchema.properties,
370
- ...dateRangeProperties
371
- },
372
- required: ["id"],
373
- additionalProperties: false
374
- }
375
- },
376
- {
377
- name: "lighthouse_portfolio_metrics_workItemAge",
378
- description: "Get per-item work item age over time for a portfolio by ID. Returns daily snapshots with each in-progress item's age in days derived from its startedDate. Items without a startedDate are omitted.",
379
- inputSchema: {
380
- type: "object",
381
- properties: {
382
- ...idInputSchema.properties,
383
- ...dateRangeProperties
384
- },
385
- required: ["id"],
386
- additionalProperties: false
387
- }
388
- },
389
- {
390
- name: "lighthouse_portfolio_metrics_totalWorkItemAge",
391
- description: "Get the total (summed) work item age over time for a portfolio by ID. Returns daily totals of all in-progress item ages derived from startedDate. Items without a startedDate are not counted.",
392
- inputSchema: {
393
- type: "object",
394
- properties: {
395
- ...idInputSchema.properties,
396
- ...dateRangeProperties
397
- },
398
- required: ["id"],
399
- additionalProperties: false
400
- }
401
- },
402
- {
403
- name: "lighthouse_feature_get",
404
- description: "Get feature details by numeric IDs or external reference IDs.",
405
- inputSchema: {
406
- type: "object",
407
- properties: {
408
- ids: {
409
- type: "array",
410
- items: {
411
- type: "integer"
412
- },
413
- description: "Feature IDs."
414
- },
415
- refs: {
416
- type: "array",
417
- items: {
418
- type: "string"
419
- },
420
- description: "Feature reference identifiers."
421
- }
422
- },
423
- additionalProperties: false
424
- }
425
- },
426
- {
427
- name: "lighthouse_feature_workitems",
428
- description: "Get work items linked to a feature by ID.",
429
- inputSchema: idInputSchema
430
- },
431
- {
432
- name: "lighthouse_delivery_list",
433
- description: "List deliveries for a portfolio by portfolio ID.",
434
- inputSchema: idInputSchema
435
- },
436
- {
437
- name: "lighthouse_delivery_metrics",
438
- description: `Get a delivery's recorded trend by delivery ID: one row per day with total, done and remaining work, the epic count and the likelihood. Set detail to "epics" for the per-epic breakdown and the forecast distribution, which are far larger. Forward-only, so it starts at the first recorded snapshot. Requires Lighthouse newer than v26.5.29.5.`,
439
- inputSchema: {
440
- type: "object",
441
- properties: {
442
- id: { type: "number", description: "Delivery ID." },
443
- detail: {
444
- type: "string",
445
- enum: ["epics"],
446
- description: 'Omit for one row per day. "epics" returns the whole recorded payload.'
447
- }
448
- },
449
- required: ["id"],
450
- additionalProperties: false
451
- }
452
- },
453
- {
454
- name: "lighthouse_blackout_list",
455
- description: "List the recurring blackout rules (recurring non-working days excluded from forecasts). Requires Lighthouse newer than v26.5.29.5.",
456
- inputSchema: emptyInputSchema
457
- },
458
- {
459
- name: "lighthouse_blackout_create",
460
- description: "Create a recurring blackout rule (weekdays + every-N-weeks interval + start date + optional open end). Premium, system-admin only. Requires Lighthouse newer than v26.5.29.5.",
461
- inputSchema: {
462
- type: "object",
463
- properties: {
464
- ...recurringBlackoutRuleProperties
465
- },
466
- required: ["weekdays", "intervalWeeks", "start", "description"],
467
- additionalProperties: false
468
- }
469
- },
470
- {
471
- name: "lighthouse_blackout_update",
472
- description: "Update a recurring blackout rule by ID. Premium, system-admin only. Requires Lighthouse newer than v26.5.29.5.",
473
- inputSchema: {
474
- type: "object",
475
- properties: {
476
- ...idInputSchema.properties,
477
- ...recurringBlackoutRuleProperties
478
- },
479
- required: ["id", "weekdays", "intervalWeeks", "start", "description"],
480
- additionalProperties: false
481
- }
482
- },
483
- {
484
- name: "lighthouse_blackout_delete",
485
- description: "Delete a recurring blackout rule by ID. Premium, system-admin only. Requires Lighthouse newer than v26.5.29.5.",
486
- inputSchema: idInputSchema
487
- },
488
- {
489
- name: "lighthouse_forecast_manual",
490
- description: "Run a manual forecast for a team by ID with optional remaining items and target date. Pass applyFilterOverride=true to apply the team's forecast filter, false to skip it, or omit to respect the team setting (Lighthouse v26.5.24.10+). The response includes filterApplied (boolean) and excludedSummary (string) when a filter was applied.",
491
- inputSchema: {
492
- type: "object",
493
- properties: {
494
- ...idInputSchema.properties,
495
- remainingItems: {
496
- type: "integer",
497
- description: "Remaining items to forecast."
498
- },
499
- targetDate: {
500
- type: "string",
501
- description: "Optional target date in ISO format (YYYY-MM-DD)."
502
- },
503
- ...forecastFilterOverrideProperty
504
- },
505
- required: ["id"],
506
- additionalProperties: false
507
- }
508
- },
509
- {
510
- name: "lighthouse_forecast_backtest",
511
- description: "Run a forecast backtest for a team by ID using forecast and historical date ranges. Pass applyFilterOverride=true to apply the team's forecast filter, false to skip it, or omit to respect the team setting (Lighthouse v26.5.24.10+). The response includes filterApplied (boolean) and excludedSummary (string) when a filter was applied.",
512
- inputSchema: {
513
- type: "object",
514
- properties: {
515
- ...idInputSchema.properties,
516
- startDate: {
517
- type: "string",
518
- description: "Forecast start date in ISO format (YYYY-MM-DD)."
519
- },
520
- endDate: {
521
- type: "string",
522
- description: "Forecast end date in ISO format (YYYY-MM-DD)."
523
- },
524
- historicalStartDate: {
525
- type: "string",
526
- description: "Historical sample start date in ISO format (YYYY-MM-DD)."
527
- },
528
- historicalEndDate: {
529
- type: "string",
530
- description: "Historical sample end date in ISO format (YYYY-MM-DD)."
531
- },
532
- ...forecastFilterOverrideProperty
533
- },
534
- required: [
535
- "id",
536
- "startDate",
537
- "endDate",
538
- "historicalStartDate",
539
- "historicalEndDate"
540
- ],
541
- additionalProperties: false
542
- }
543
- },
544
- {
545
- name: "lighthouse_team_metrics_cumulativeStateTime",
546
- description: "Get cumulative time-per-state bar data for a team by ID: one entry per Doing-category workflow state with total/completed/ongoing contribution days and item counts. Optionally filter by date range and a subset of work-item IDs.",
547
- inputSchema: {
548
- type: "object",
549
- properties: {
550
- ...idInputSchema.properties,
551
- ...dateRangeProperties,
552
- ...itemIdsProperty
553
- },
554
- required: ["id"],
555
- additionalProperties: false
556
- }
557
- },
558
- {
559
- name: "lighthouse_team_metrics_cumulativeStateTimeItems",
560
- description: "Get the per-item drill-down for ONE state of a team's cumulative time-per-state chart: the work items that contributed to that state, with days contributed. Requires the state name.",
561
- inputSchema: {
562
- type: "object",
563
- properties: {
564
- ...idInputSchema.properties,
565
- ...cumulativeStateProperty,
566
- ...dateRangeProperties,
567
- ...itemIdsProperty
568
- },
569
- required: ["id", "state"],
570
- additionalProperties: false
571
- }
572
- },
573
- {
574
- name: "lighthouse_team_metrics_cumulativeStateTimeCandidates",
575
- description: "List the work items selectable in a team's cumulative time-per-state item picker for the window (the in-scope candidate set). Search keys are referenceId and title.",
576
- inputSchema: {
577
- type: "object",
578
- properties: {
579
- ...idInputSchema.properties,
580
- ...dateRangeProperties
581
- },
582
- required: ["id"],
583
- additionalProperties: false
584
- }
585
- },
586
- {
587
- name: "lighthouse_portfolio_metrics_cumulativeStateTime",
588
- description: "Get cumulative time-per-state bar data for a portfolio by ID: one entry per Doing-category workflow state with total/completed/ongoing contribution days and item counts. Optionally filter by date range and a subset of work-item IDs.",
589
- inputSchema: {
590
- type: "object",
591
- properties: {
592
- ...idInputSchema.properties,
593
- ...dateRangeProperties,
594
- ...itemIdsProperty
595
- },
596
- required: ["id"],
597
- additionalProperties: false
598
- }
599
- },
600
- {
601
- name: "lighthouse_portfolio_metrics_cumulativeStateTimeItems",
602
- description: "Get the per-item drill-down for ONE state of a portfolio's cumulative time-per-state chart: the work items that contributed to that state, with days contributed. Requires the state name.",
603
- inputSchema: {
604
- type: "object",
605
- properties: {
606
- ...idInputSchema.properties,
607
- ...cumulativeStateProperty,
608
- ...dateRangeProperties,
609
- ...itemIdsProperty
610
- },
611
- required: ["id", "state"],
612
- additionalProperties: false
613
- }
614
- },
615
- {
616
- name: "lighthouse_portfolio_metrics_cumulativeStateTimeCandidates",
617
- description: "List the work items selectable in a portfolio's cumulative time-per-state item picker for the window (the in-scope candidate set). Search keys are referenceId and title.",
618
- inputSchema: {
619
- type: "object",
620
- properties: {
621
- ...idInputSchema.properties,
622
- ...dateRangeProperties
623
- },
624
- required: ["id"],
625
- additionalProperties: false
626
- }
627
- }
54
+ const isToolResult = (value) => typeof value === "object" && value !== null && "isError" in value && "content" in value;
55
+ const getNumericId = (argumentsPayload) => {
56
+ if (typeof argumentsPayload !== "object" || argumentsPayload === null || Array.isArray(argumentsPayload)) return null;
57
+ const value = argumentsPayload.id;
58
+ if (typeof value === "number" && Number.isInteger(value)) return value;
59
+ return null;
60
+ };
61
+ //#endregion
62
+ //#region src/usageDataPort.ts
63
+ const USAGE_DATA_ELICITATION_MESSAGE = [
64
+ "May Lighthouse send usage data?",
65
+ "The Lighthouse MCP server tells your Lighthouse which tools you use (never names, ids, URLs or anything you typed), so we can see what helps.",
66
+ "Details: https://docs.lighthouse.letpeople.work/settings/usagedata.html",
67
+ "(change any time: lh config usage-data on|off)"
68
+ ].join("\n");
69
+ const USAGE_DATA_QUESTION_TIMEOUT_MS = 5e4;
70
+ const ANSWERS = {
71
+ accept: "yes",
72
+ decline: "no",
73
+ cancel: null
74
+ };
75
+ /**
76
+ * Puts the usage data question through the assistant. Accepting is a yes and declining a No; a cancel, a
77
+ * closed question, an error or no answer in time is no answer at all.
78
+ */
79
+ const askThroughTheAssistant = (send) => async () => {
80
+ try {
81
+ const { action } = await send({
82
+ message: USAGE_DATA_ELICITATION_MESSAGE,
83
+ requestedSchema: {
84
+ type: "object",
85
+ properties: {}
86
+ }
87
+ }, { timeout: USAGE_DATA_QUESTION_TIMEOUT_MS });
88
+ return ANSWERS[action] ?? null;
89
+ } catch {
90
+ return null;
91
+ }
92
+ };
93
+ const QUESTION_RETRY_INTERVAL_MS = 36e5;
94
+ /**
95
+ * A port that puts the question at most once in this process. Only a call that might ask waits for its
96
+ * step; every other call, and every call once the question has been put, hands its step over and returns.
97
+ * Calls that reach the question while it is being put get no answer at once rather than a second question.
98
+ */
99
+ const askingOnceInThisProcess = (dependencies) => {
100
+ let asked = false;
101
+ let notAskableUntil = Number.NEGATIVE_INFINITY;
102
+ const now = () => (dependencies.now?.() ?? /* @__PURE__ */ new Date()).getTime();
103
+ const once = (ask) => async () => {
104
+ if (asked) return null;
105
+ asked = true;
106
+ return ask();
107
+ };
108
+ const settleQuietly = async (step) => {
109
+ try {
110
+ await dependencies.settle(step);
111
+ } catch {}
112
+ };
113
+ const mightAskQuietly = async () => {
114
+ try {
115
+ return await dependencies.mightAsk();
116
+ } catch {
117
+ return false;
118
+ }
119
+ };
120
+ return async (step) => {
121
+ const { ask, ...withoutAsk } = step;
122
+ if (ask === void 0 || asked || !step.reached || now() < notAskableUntil || !await mightAskQuietly()) {
123
+ settleQuietly(withoutAsk);
124
+ return;
125
+ }
126
+ await settleQuietly({
127
+ ...withoutAsk,
128
+ ask: once(ask)
129
+ });
130
+ if (!asked) notAskableUntil = now() + QUESTION_RETRY_INTERVAL_MS;
131
+ };
132
+ };
133
+ const TOOL_EVENTS = {
134
+ lighthouse_forecast_manual: "TeamManualForecastRun",
135
+ lighthouse_team_refresh: "TeamRefreshTriggered",
136
+ lighthouse_portfolio_refresh: "PortfolioRefreshTriggered"
137
+ };
138
+ /** What a tool call that succeeded did that the web counts too. */
139
+ const usageDataOccurrencesOf = (toolName) => {
140
+ const name = TOOL_EVENTS[toolName];
141
+ return name === void 0 ? [] : [{ name }];
142
+ };
143
+ const countedToolResult = (result, occurrences) => ({
144
+ result,
145
+ occurrences: result.isError ? [] : occurrences
146
+ });
147
+ /**
148
+ * A vote Lighthouse took, at the moment the Refinement read before it says, and the Work Item becoming
149
+ * Ready when only Lighthouse's answer can say this vote made it so.
150
+ */
151
+ const usageDataOccurrencesOfAVote = (readBeforeTheVote, answered) => {
152
+ if (!answered.ok) return [];
153
+ const sizingMoment = (0, _letpeoplework_lighthouse_client.sizingMomentOf)(readBeforeTheVote);
154
+ const cast = {
155
+ name: "TeamSizingVoteCast",
156
+ sizingMoment
157
+ };
158
+ return answered.value.madeReady ? [cast, {
159
+ name: "TeamSizingReadinessReached",
160
+ sizingMoment
161
+ }] : [cast];
162
+ };
163
+ /** The verdict a Refinement Lighthouse answered shows, when it is a Refinement day's. */
164
+ const usageDataOccurrencesOfARead = (answered) => {
165
+ if (!answered.ok) return [];
166
+ const refinementVerdict = (0, _letpeoplework_lighthouse_client.refinementDayVerdictOf)({
167
+ isRefinementDay: answered.value.isRefinementDay,
168
+ workItemsListed: answered.value.workItems.length,
169
+ verdict: answered.value.need.verdict
170
+ });
171
+ return refinementVerdict === null ? [] : [{
172
+ name: "TeamRefinementDayVerdictShown",
173
+ refinementVerdict
174
+ }];
175
+ };
176
+ //#endregion
177
+ //#region src/refinementTools.ts
178
+ const CHANNEL = "Assistant";
179
+ const ASK_FOR_THE_NAME = "Ask the user for their name and send it as voterName; never guess it.";
180
+ const workItemProperty = {
181
+ type: "string",
182
+ description: "The work item's reference, as referenceId shows it in lighthouse_team_refinement_get (for example GR-051)."
183
+ };
184
+ const voterNameProperty = {
185
+ type: "string",
186
+ description: "The user's own name, needed when Lighthouse runs without sign-in. Ask the user for their name and never infer it, not from the system, an account or earlier messages. Leave it out with sign-in."
187
+ };
188
+ const teamIdProperty = {
189
+ type: "integer",
190
+ description: "The team's numeric id."
191
+ };
192
+ const refinementWriteToolDefinitions = [
193
+ {
194
+ name: "lighthouse_team_refinement_vote",
195
+ description: "Records the USER's own sizing judgement under their name. Never call this on your own initiative or on someone else's behalf: show the user the Work Item, the answer and any comment you intend to send, and call only after they explicitly confirm. answer is Yes (ready to be pulled), YesBut (\"Yes, if…\": ready under a condition, which goes in comment) or No; voting again replaces the user's earlier vote. Returns the work item as the vote left it, with `summary` stating where it now stands. Lighthouse newer than v26.10.3.6.",
196
+ inputSchema: {
197
+ type: "object",
198
+ properties: {
199
+ id: teamIdProperty,
200
+ workItem: workItemProperty,
201
+ answer: {
202
+ type: "string",
203
+ enum: [..._letpeoplework_lighthouse_client.REFINEMENT_ANSWERS],
204
+ description: "The user's answer: Yes, YesBut (\"Yes, if…\") or No, as myVote and split name them."
205
+ },
206
+ comment: {
207
+ type: "string",
208
+ description: "Optional; required with YesBut, where it says what has to be true."
209
+ },
210
+ voterName: voterNameProperty
211
+ },
212
+ required: [
213
+ "id",
214
+ "workItem",
215
+ "answer"
216
+ ],
217
+ additionalProperties: false
218
+ }
219
+ },
220
+ {
221
+ name: "lighthouse_team_refinement_comment",
222
+ description: "Records a comment or question of the USER's on a work item in refinement, under their name, without a vote. Never call this on your own initiative or on someone else's behalf: show the user the work item and the comment you intend to send, and call only after they explicitly confirm. Returns the work item as the comment left it, with a `summary`. Lighthouse newer than v26.10.3.6.",
223
+ inputSchema: {
224
+ type: "object",
225
+ properties: {
226
+ id: teamIdProperty,
227
+ workItem: workItemProperty,
228
+ comment: {
229
+ type: "string",
230
+ description: "The user's comment."
231
+ },
232
+ voterName: voterNameProperty
233
+ },
234
+ required: [
235
+ "id",
236
+ "workItem",
237
+ "comment"
238
+ ],
239
+ additionalProperties: false
240
+ }
241
+ },
242
+ {
243
+ name: "lighthouse_team_refinement_voteTakeBack",
244
+ description: "Takes back the vote the USER cast on a work item from this assistant. Call only when the user asks for it. When this assistant holds no vote of theirs on the work item it says so and takes nothing back. Returns the work item as the take-back left it, with `summary` stating where it now stands. Lighthouse newer than v26.10.3.6.",
245
+ inputSchema: {
246
+ type: "object",
247
+ properties: {
248
+ id: teamIdProperty,
249
+ workItem: workItemProperty
250
+ },
251
+ required: ["id", "workItem"],
252
+ additionalProperties: false
253
+ }
254
+ }
628
255
  ];
629
- var encodePayload = (value) => {
630
- try {
631
- return (0, import_toon.encode)(value);
632
- } catch {
633
- return JSON.stringify(value);
634
- }
635
- };
636
- var getSuccessToolResult = (text) => ({
637
- isError: false,
638
- content: [
639
- {
640
- type: "text",
641
- text
642
- }
643
- ]
256
+ const refinementWriteInputSchemas = {
257
+ lighthouse_team_refinement_vote: zod.z.object({
258
+ id: zod.z.number().int(),
259
+ workItem: zod.z.string(),
260
+ answer: zod.z.enum(_letpeoplework_lighthouse_client.REFINEMENT_ANSWERS),
261
+ comment: zod.z.string().optional(),
262
+ voterName: zod.z.string().optional()
263
+ }),
264
+ lighthouse_team_refinement_comment: zod.z.object({
265
+ id: zod.z.number().int(),
266
+ workItem: zod.z.string(),
267
+ comment: zod.z.string(),
268
+ voterName: zod.z.string().optional()
269
+ }),
270
+ lighthouse_team_refinement_voteTakeBack: zod.z.object({
271
+ id: zod.z.number().int(),
272
+ workItem: zod.z.string()
273
+ })
274
+ };
275
+ const getArgument = (argumentsPayload, key) => typeof argumentsPayload === "object" && argumentsPayload !== null && !Array.isArray(argumentsPayload) ? argumentsPayload[key] : void 0;
276
+ const getNonBlankArgument = (argumentsPayload, key) => {
277
+ const value = getArgument(argumentsPayload, key);
278
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
279
+ };
280
+ /** The team and work item a write names, or the house "invalid …" refusal under its label. */
281
+ const getRefinementWriteTarget = (argumentsPayload, label) => {
282
+ const teamId = getNumericId(argumentsPayload);
283
+ if (teamId === null) return getErrorToolResult(`${label}: invalid id`);
284
+ const workItem = getNonBlankArgument(argumentsPayload, "workItem");
285
+ if (workItem === void 0) return getErrorToolResult(`${label}: invalid workItem`);
286
+ return {
287
+ teamId,
288
+ workItem
289
+ };
290
+ };
291
+ /**
292
+ * The key this server keeps, minted and kept on its first write; none on a server that keeps none; or why
293
+ * it could not be kept.
294
+ */
295
+ const keepServerVoterKey = async (label, store) => {
296
+ if (store === void 0) return;
297
+ try {
298
+ return await (0, _letpeoplework_lighthouse_client.keepVoterKey)(store);
299
+ } catch (error) {
300
+ return getErrorToolResult(`${label}: ${error instanceof Error ? error.message : "the voter key was not kept."}`);
301
+ }
302
+ };
303
+ const loadServerVoterKey = async (dependencies) => await dependencies.voterKeyStore?.load() ?? void 0;
304
+ const getVoteRefusalToolResult = async (label, error, client, dependencies) => getErrorToolResult(`${label}: ${await (0, _letpeoplework_lighthouse_client.readVoteRefusal)(client, error, ASK_FOR_THE_NAME, dependencies.voterKeyRequired)}`);
305
+ const isSignedIn = (refinement) => refinement.voterIdentity === "Account";
306
+ const readRefinementFor = async (label, client, dependencies, teamId, voterKey) => {
307
+ const refinement = await client.getTeamRefinement(teamId, { voterKey });
308
+ return refinement.ok ? refinement.value : getVoteRefusalToolResult(label, refinement.error, client, dependencies);
309
+ };
310
+ const resolveWriter = async (label, argumentsPayload, client, dependencies, teamId) => {
311
+ const refinement = await readRefinementFor(label, client, dependencies, teamId);
312
+ if (isToolResult(refinement)) return refinement;
313
+ if (isSignedIn(refinement)) return {
314
+ voter: {},
315
+ refinement
316
+ };
317
+ const voterName = getNonBlankArgument(argumentsPayload, "voterName");
318
+ if (voterName === void 0) return getErrorToolResult(`${label}: ${ASK_FOR_THE_NAME}`);
319
+ const voterKey = await keepServerVoterKey(label, dependencies.voterKeyStore);
320
+ return isToolResult(voterKey) ? voterKey : {
321
+ voter: {
322
+ voterName,
323
+ voterKey
324
+ },
325
+ refinement
326
+ };
327
+ };
328
+ const getRefinementWriteToolResult = async (label, result, client, dependencies, describe) => {
329
+ if (!result.ok) return getVoteRefusalToolResult(label, result.error, client, dependencies);
330
+ const written = {
331
+ summary: describe(result.value),
332
+ ...result.value
333
+ };
334
+ return getSuccessToolResult(`${label}: ${encodePayload(written)}`);
335
+ };
336
+ const refusedVoting = async (label, dependencies) => {
337
+ const refusal = await dependencies.refuseVoting?.() ?? null;
338
+ return refusal === null ? null : getErrorToolResult(`${label}: ${refusal}`);
339
+ };
340
+ const getRefinementErrorToolResult = (error) => getErrorToolResult(`refinement: ${error.category} (${error.reason})`);
341
+ const getRefinement = async (argumentsPayload, client, dependencies) => {
342
+ const teamId = getNumericId(argumentsPayload);
343
+ if (teamId === null) return getErrorToolResult("refinement: invalid id");
344
+ const voterKey = await loadServerVoterKey(dependencies);
345
+ const [refinement, wording] = await Promise.all([client.getTeamRefinement(teamId, { voterKey }), (0, _letpeoplework_lighthouse_client.readRefinementWording)(client, teamId)]);
346
+ if (!refinement.ok) return getRefinementErrorToolResult(refinement.error);
347
+ if (!wording.ok) return getRefinementErrorToolResult(wording.error);
348
+ const summary = (0, _letpeoplework_lighthouse_client.describeRefinementSummary)(refinement.value, wording.value);
349
+ return countedToolResult(getSuccessToolResult(`refinement: ${encodePayload({
350
+ summary,
351
+ ...refinement.value
352
+ })}`), usageDataOccurrencesOfARead(refinement));
353
+ };
354
+ const castVote = async (argumentsPayload, client, dependencies) => {
355
+ const target = getRefinementWriteTarget(argumentsPayload, "vote");
356
+ if (isToolResult(target)) return target;
357
+ const answer = getArgument(argumentsPayload, "answer");
358
+ if (!(0, _letpeoplework_lighthouse_client.isRefinementAnswer)(answer)) return getErrorToolResult("vote: invalid answer");
359
+ const comment = getNonBlankArgument(argumentsPayload, "comment");
360
+ if ((0, _letpeoplework_lighthouse_client.isMissingItsCondition)(answer, comment)) return getErrorToolResult(`vote: ${(0, _letpeoplework_lighthouse_client.describeMissingCondition)("add a comment saying what has to be true.")}`);
361
+ const refused = await refusedVoting("vote", dependencies);
362
+ if (refused !== null) return refused;
363
+ const writer = await resolveWriter("vote", argumentsPayload, client, dependencies, target.teamId);
364
+ if (isToolResult(writer)) return writer;
365
+ const { voter, refinement } = writer;
366
+ const result = await client.castRefinementVote(target.teamId, target.workItem, {
367
+ answer,
368
+ channel: CHANNEL,
369
+ comment,
370
+ ...voter
371
+ });
372
+ return countedToolResult(await getRefinementWriteToolResult("vote", result, client, dependencies, (row) => (0, _letpeoplework_lighthouse_client.describeRecordedVote)({
373
+ workItem: target.workItem,
374
+ voterName: voter.voterName
375
+ }, answer, row)), usageDataOccurrencesOfAVote(refinement, result));
376
+ };
377
+ const addComment = async (argumentsPayload, client, dependencies) => {
378
+ const target = getRefinementWriteTarget(argumentsPayload, "comment");
379
+ if (isToolResult(target)) return target;
380
+ const comment = getNonBlankArgument(argumentsPayload, "comment");
381
+ if (comment === void 0) return getErrorToolResult("comment: invalid comment");
382
+ const refused = await refusedVoting("comment", dependencies);
383
+ if (refused !== null) return refused;
384
+ const writer = await resolveWriter("comment", argumentsPayload, client, dependencies, target.teamId);
385
+ if (isToolResult(writer)) return writer;
386
+ const { voter } = writer;
387
+ const result = await client.addRefinementComment(target.teamId, target.workItem, {
388
+ comment,
389
+ channel: CHANNEL,
390
+ ...voter
391
+ });
392
+ return getRefinementWriteToolResult("comment", result, client, dependencies, () => (0, _letpeoplework_lighthouse_client.describeRecordedComment)({
393
+ workItem: target.workItem,
394
+ voterName: voter.voterName
395
+ }));
396
+ };
397
+ const takeBackVote = async (argumentsPayload, client, dependencies) => {
398
+ const target = getRefinementWriteTarget(argumentsPayload, "takeBack");
399
+ if (isToolResult(target)) return target;
400
+ const refused = await refusedVoting("takeBack", dependencies);
401
+ if (refused !== null) return refused;
402
+ const nothingToTakeBack = getSuccessToolResult(`takeBack: ${encodePayload({ summary: (0, _letpeoplework_lighthouse_client.describeNothingToTakeBack)(target.workItem) })}`);
403
+ const keptVoterKey = await loadServerVoterKey(dependencies);
404
+ const refinement = await readRefinementFor("takeBack", client, dependencies, target.teamId, keptVoterKey);
405
+ if (isToolResult(refinement)) return refinement;
406
+ const myVote = (0, _letpeoplework_lighthouse_client.myVoteOn)(refinement, target.workItem);
407
+ if (myVote === null) return nothingToTakeBack;
408
+ const signedIn = isSignedIn(refinement);
409
+ const voterName = signedIn ? null : await (0, _letpeoplework_lighthouse_client.readNameOfMyVote)(client, target.teamId, target.workItem, keptVoterKey);
410
+ const result = await client.takeBackRefinementVote(target.teamId, target.workItem, {
411
+ channel: CHANNEL,
412
+ voterKey: signedIn ? void 0 : keptVoterKey,
413
+ answer: myVote
414
+ });
415
+ return getRefinementWriteToolResult("takeBack", result, client, dependencies, (takenBack) => (0, _letpeoplework_lighthouse_client.describeTakenBack)({
416
+ workItem: target.workItem,
417
+ voterName
418
+ }, takenBack));
419
+ };
420
+ const REFINEMENT_TOOLS = /* @__PURE__ */ new Map([
421
+ ["lighthouse_team_refinement_get", getRefinement],
422
+ ["lighthouse_team_refinement_vote", castVote],
423
+ ["lighthouse_team_refinement_comment", addComment],
424
+ ["lighthouse_team_refinement_voteTakeBack", takeBackVote]
425
+ ]);
426
+ /** The handler of a refinement tool, or undefined for a tool that is not one. */
427
+ const findRefinementTool = (name) => REFINEMENT_TOOLS.get(name);
428
+ //#endregion
429
+ //#region src/index.ts
430
+ const getMcpCorePackageContract = () => ({
431
+ name: "@letpeoplework/lighthouse-mcp-core",
432
+ dependsOn: "@letpeoplework/lighthouse-client",
433
+ transports: ["stdio", "streamable-http"]
644
434
  });
645
- var getErrorToolResult = (text) => ({
646
- isError: true,
647
- content: [
648
- {
649
- type: "text",
650
- text
651
- }
652
- ]
435
+ const emptyInputSchema = {
436
+ type: "object",
437
+ properties: {},
438
+ additionalProperties: false
439
+ };
440
+ const idInputSchema = {
441
+ type: "object",
442
+ properties: { id: {
443
+ type: "integer",
444
+ description: "Unique numeric identifier."
445
+ } },
446
+ required: ["id"],
447
+ additionalProperties: false
448
+ };
449
+ const dateRangeProperties = {
450
+ startDate: {
451
+ type: "string",
452
+ description: "Inclusive start date in ISO format (YYYY-MM-DD)."
453
+ },
454
+ endDate: {
455
+ type: "string",
456
+ description: "Inclusive end date in ISO format (YYYY-MM-DD)."
457
+ }
458
+ };
459
+ const throughputFilterViewProperty = { view: {
460
+ type: "string",
461
+ enum: ["raw", "filtered"],
462
+ description: "Forecast-filter view (Lighthouse v26.5.24.10+). \"filtered\" applies the team's exclusion rule to throughput. Omit or \"raw\" returns unfiltered data."
463
+ } };
464
+ const percentilesOverTimeProperties = {
465
+ metricType: {
466
+ type: "string",
467
+ enum: ["CycleTime", "WorkItemAge"],
468
+ description: "Percentile family to read. Defaults to \"CycleTime\" when omitted."
469
+ },
470
+ horizon: {
471
+ type: "number",
472
+ description: "Cycle-time horizon in days (30, 60 or 90). Ignored for WorkItemAge, which is always as-of-today and has no horizon dimension."
473
+ }
474
+ };
475
+ const processBehaviorOverTimeProperties = { metricType: {
476
+ type: "string",
477
+ enum: [
478
+ "Throughput",
479
+ "WorkItemAge",
480
+ "Wip",
481
+ "CycleTime",
482
+ "Arrivals",
483
+ "FeatureSize"
484
+ ],
485
+ description: "Process-behaviour family to read. Defaults to \"Throughput\" when omitted. \"FeatureSize\" is portfolio-only — a team never records it and returns an empty series."
486
+ } };
487
+ const processBehaviorMetricTypes = [
488
+ "Throughput",
489
+ "WorkItemAge",
490
+ "Wip",
491
+ "CycleTime",
492
+ "Arrivals",
493
+ "FeatureSize"
494
+ ];
495
+ const TEAM_CHART_TYPES = [
496
+ "Throughput",
497
+ "WorkItemAge",
498
+ "Wip",
499
+ "CycleTime",
500
+ "Arrivals"
501
+ ];
502
+ const chartTypeProperty = (chartTypes) => ({ metricType: {
503
+ type: "string",
504
+ enum: chartTypes,
505
+ description: "Which Process Behaviour Chart to read."
506
+ } });
507
+ const chartInputSchema = (chartTypes) => ({
508
+ type: "object",
509
+ properties: {
510
+ ...idInputSchema.properties,
511
+ ...chartTypeProperty(chartTypes),
512
+ ...dateRangeProperties
513
+ },
514
+ required: ["id", "metricType"],
515
+ additionalProperties: false
653
516
  });
654
- var getNumericId = (argumentsPayload) => {
655
- if (typeof argumentsPayload !== "object" || argumentsPayload === null || Array.isArray(argumentsPayload)) {
656
- return null;
657
- }
658
- const value = argumentsPayload.id;
659
- if (typeof value === "number" && Number.isInteger(value)) {
660
- return value;
661
- }
662
- return null;
663
- };
664
- var getDefinitionId = (argumentsPayload) => {
665
- if (typeof argumentsPayload !== "object" || argumentsPayload === null || Array.isArray(argumentsPayload)) {
666
- return void 0;
667
- }
668
- const value = argumentsPayload.definitionId;
669
- return typeof value === "number" && Number.isInteger(value) ? value : void 0;
670
- };
671
- var isObjectRecord = (value) => {
672
- return typeof value === "object" && value !== null && !Array.isArray(value);
673
- };
674
- var getDateRange = (argumentsPayload) => {
675
- if (!isObjectRecord(argumentsPayload)) {
676
- return void 0;
677
- }
678
- const startDate = argumentsPayload.startDate;
679
- const endDate = argumentsPayload.endDate;
680
- if (typeof startDate === "string" && typeof endDate === "string") {
681
- return { startDate, endDate };
682
- }
683
- return void 0;
684
- };
685
- var getStringArgument = (argumentsPayload, key) => {
686
- if (!isObjectRecord(argumentsPayload)) {
687
- return void 0;
688
- }
689
- const value = argumentsPayload[key];
690
- return typeof value === "string" ? value : void 0;
691
- };
692
- var getItemIdsArgument = (argumentsPayload) => {
693
- if (!isObjectRecord(argumentsPayload)) {
694
- return void 0;
695
- }
696
- const value = argumentsPayload.itemIds;
697
- if (!Array.isArray(value)) {
698
- return void 0;
699
- }
700
- const ids = value.filter(
701
- (entry) => typeof entry === "number" && Number.isInteger(entry)
702
- );
703
- return ids.length > 0 ? ids : void 0;
704
- };
705
- var getThroughputFilterView = (argumentsPayload) => {
706
- if (!isObjectRecord(argumentsPayload)) {
707
- return void 0;
708
- }
709
- const view = argumentsPayload.view;
710
- return view === "raw" || view === "filtered" ? view : void 0;
711
- };
712
- var percentilesOverTimeMetricTypes = ["CycleTime", "WorkItemAge"];
713
- var getPercentilesOverTimeMetricType = (argumentsPayload) => {
714
- const raw = getStringArgument(argumentsPayload, "metricType");
715
- return percentilesOverTimeMetricTypes.find((candidate) => candidate === raw);
716
- };
717
- var processBehaviorMetricTypes = [
718
- "Throughput",
719
- "WorkItemAge",
720
- "Wip",
721
- "CycleTime",
722
- "Arrivals",
723
- "FeatureSize"
517
+ const cumulativeStateProperty = { state: {
518
+ type: "string",
519
+ description: "Workflow state name to drill into (must match a bar's state from the cumulativeStateTime response)."
520
+ } };
521
+ const itemIdsProperty = { itemIds: {
522
+ type: "array",
523
+ items: { type: "integer" },
524
+ description: "Optional work-item IDs to narrow the computation to a selected subset; omit for all in-scope items."
525
+ } };
526
+ const forecastFilterOverrideProperty = { applyFilterOverride: {
527
+ type: "boolean",
528
+ description: "Override the team's forecast-filter setting (Lighthouse v26.5.24.10+). true = apply the team filter; false = skip the filter (raw); omit = respect the team setting."
529
+ } };
530
+ const recurringBlackoutRuleProperties = {
531
+ weekdays: {
532
+ type: "array",
533
+ items: {
534
+ type: "string",
535
+ enum: [
536
+ "Sunday",
537
+ "Monday",
538
+ "Tuesday",
539
+ "Wednesday",
540
+ "Thursday",
541
+ "Friday",
542
+ "Saturday"
543
+ ]
544
+ },
545
+ description: "Weekdays the rule applies to."
546
+ },
547
+ intervalWeeks: {
548
+ type: "integer",
549
+ description: "Repeat every N weeks (1 = every week)."
550
+ },
551
+ start: {
552
+ type: "string",
553
+ description: "Rule start date in ISO format (YYYY-MM-DD)."
554
+ },
555
+ end: {
556
+ type: ["string", "null"],
557
+ description: "Optional end date in ISO format (YYYY-MM-DD), or null for an open end."
558
+ },
559
+ description: {
560
+ type: "string",
561
+ description: "Human-readable description of the rule."
562
+ }
563
+ };
564
+ const toolDefinitions = [
565
+ {
566
+ name: "lighthouse_health_check",
567
+ description: "Check connectivity to Lighthouse and return whether the configured endpoint is reachable. A second text block, `summary`, says that Lighthouse is reachable; the first block is the facts, unchanged.",
568
+ inputSchema: emptyInputSchema
569
+ },
570
+ {
571
+ name: "lighthouse_version_get",
572
+ description: "Retrieve the Lighthouse server version from the version endpoint. A second text block, `summary`, names it as the web's footer does; the first block is the facts, unchanged.",
573
+ inputSchema: emptyInputSchema
574
+ },
575
+ {
576
+ name: "lighthouse_worktracking_list",
577
+ description: "List configured work-tracking system connections in Lighthouse. A second text block, `summary`, counts them in the instance's terminology; the first block is the facts, unchanged.",
578
+ inputSchema: emptyInputSchema
579
+ },
580
+ {
581
+ name: "lighthouse_worktracking_get",
582
+ description: "Get a single work-tracking system connection by ID. `summary` names the connection and its type as the Overview does, and never carries an option's value.",
583
+ inputSchema: idInputSchema
584
+ },
585
+ {
586
+ name: "lighthouse_team_list",
587
+ description: "List all teams available in Lighthouse. A second text block, `summary`, counts them in the instance's terminology; the first block is the facts, unchanged.",
588
+ inputSchema: emptyInputSchema
589
+ },
590
+ {
591
+ name: "lighthouse_team_get",
592
+ description: "Get full details for one team by ID. `summary` states the page's heading and settings as the web does, in the instance's terminology.",
593
+ inputSchema: idInputSchema
594
+ },
595
+ {
596
+ name: "lighthouse_team_refresh",
597
+ description: "Trigger data refresh for a team by ID. A second text block, `summary`, confirms it in one line as lh does, in the instance's terminology; the first block is the facts, unchanged.",
598
+ inputSchema: idInputSchema
599
+ },
600
+ {
601
+ name: "lighthouse_team_refinement_get",
602
+ description: "How many work items a team should refine before its next Refinement, as on the team's Refinement tab (Lighthouse newer than v26.10.3.6). `summary` is the sentence the web page states, in the instance's terminology. need.low and need.high are the range of work items the team is likely to pull over one cycle (need.cycleStart to need.cycleEnd: from the next Refinement to the one after, or from today on a Refinement day), read at need.lowPercentile and need.highPercentile. need.verdict says where readyCount sits against that range: Below, In or Above. Without a verdict, need.unavailableReason says why: NoCadence, InsufficientData or NoRefinementStates. isRefinementDay is true on a Refinement day, when the cycle starts today. daysUntilNextRefinement counts the days from the instance's today to nextRefinementDate. readySource says what readyCount counts: work items ready by Votes, or by Stages on a team with stage rules. workItems are the items in refinement, in the order the Refinement tab lists them.",
603
+ inputSchema: idInputSchema
604
+ },
605
+ ...refinementWriteToolDefinitions,
606
+ {
607
+ name: "lighthouse_portfolio_list",
608
+ description: "List all portfolios in Lighthouse. A second text block, `summary`, counts them in the instance's terminology; the first block is the facts, unchanged.",
609
+ inputSchema: emptyInputSchema
610
+ },
611
+ {
612
+ name: "lighthouse_portfolio_get",
613
+ description: "Get full details for one portfolio by ID. `summary` states the page's heading and settings as the web does, in the instance's terminology.",
614
+ inputSchema: idInputSchema
615
+ },
616
+ {
617
+ name: "lighthouse_portfolio_refresh",
618
+ description: "Trigger data refresh for a portfolio by ID. A second text block, `summary`, confirms it in one line as lh does, in the instance's terminology; the first block is the facts, unchanged.",
619
+ inputSchema: idInputSchema
620
+ },
621
+ {
622
+ name: "lighthouse_team_metrics_throughput",
623
+ description: "Get throughput run-chart data for a team by ID, optionally filtered by start and end dates. Pass view=\"filtered\" to apply the team's forecast-exclusion rule (Lighthouse v26.5.24.10+); omit or \"raw\" returns unfiltered data. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and its sentence.",
624
+ inputSchema: {
625
+ type: "object",
626
+ properties: {
627
+ ...idInputSchema.properties,
628
+ ...dateRangeProperties,
629
+ ...throughputFilterViewProperty
630
+ },
631
+ required: ["id"],
632
+ additionalProperties: false
633
+ }
634
+ },
635
+ {
636
+ name: "lighthouse_team_metrics_cycleTimePercentiles",
637
+ description: "Get cycle-time percentiles for a team by ID, optionally filtered by start and end dates. Pass definitionId to get the percentiles for a named cycle time (premium) instead of the default cycle time. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
638
+ inputSchema: {
639
+ type: "object",
640
+ properties: {
641
+ ...idInputSchema.properties,
642
+ ...dateRangeProperties,
643
+ definitionId: {
644
+ type: "number",
645
+ description: "Optional named cycle time definition ID; omit for the default cycle time."
646
+ }
647
+ },
648
+ required: ["id"],
649
+ additionalProperties: false
650
+ }
651
+ },
652
+ {
653
+ name: "lighthouse_team_metrics_workItemAgePercentiles",
654
+ description: "Get work-item age percentiles for a team by ID, optionally filtered by start and end dates. Ages are measured as of the last day of the selected range, not as of today — a historical range reports how old the items were at the end of that period, so do not present the result as the team's current ages unless the range ends today. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
655
+ inputSchema: {
656
+ type: "object",
657
+ properties: {
658
+ ...idInputSchema.properties,
659
+ ...dateRangeProperties
660
+ },
661
+ required: ["id"],
662
+ additionalProperties: false
663
+ }
664
+ },
665
+ {
666
+ name: "lighthouse_portfolio_metrics_workItemAgePercentiles",
667
+ description: "Get work-item age percentiles for a portfolio by ID, optionally filtered by start and end dates. Ages are measured as of the last day of the selected range, not as of today — a historical range reports how old the items were at the end of that period, so do not present the result as the portfolio's current ages unless the range ends today. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
668
+ inputSchema: {
669
+ type: "object",
670
+ properties: {
671
+ ...idInputSchema.properties,
672
+ ...dateRangeProperties
673
+ },
674
+ required: ["id"],
675
+ additionalProperties: false
676
+ }
677
+ },
678
+ {
679
+ name: "lighthouse_team_metrics_blockedCountHistory",
680
+ description: "Get the blocked-items-over-time trend for a team by ID: how many work items were blocked on each captured day, optionally filtered by start and end dates. To see what is blocked right now and for how long, call lighthouse_team_metrics_wip — each item carries isBlocked and, when blocked, a blockedSince timestamp. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
681
+ inputSchema: {
682
+ type: "object",
683
+ properties: {
684
+ ...idInputSchema.properties,
685
+ ...dateRangeProperties
686
+ },
687
+ required: ["id"],
688
+ additionalProperties: false
689
+ }
690
+ },
691
+ {
692
+ name: "lighthouse_portfolio_metrics_blockedCountHistory",
693
+ description: "Get the blocked-items-over-time trend for a portfolio by ID: how many work items were blocked on each captured day, optionally filtered by start and end dates. No tool reads what is blocked right now for a portfolio: name the command `lh metrics portfolio --metrics wip --id <id>`, whose items each carry isBlocked and, when blocked, a blockedSince timestamp. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
694
+ inputSchema: {
695
+ type: "object",
696
+ properties: {
697
+ ...idInputSchema.properties,
698
+ ...dateRangeProperties
699
+ },
700
+ required: ["id"],
701
+ additionalProperties: false
702
+ }
703
+ },
704
+ {
705
+ name: "lighthouse_team_metrics_percentilesOverTime",
706
+ description: "Get the percentiles-over-time trend for a team by ID: the p50/p70/p85/p95 quartet recorded on each captured day, optionally filtered by start and end dates. By default Lighthouse only returns days it recorded, so a recently upgraded server returns an empty series until it has recorded some; where a System Admin has switched on filling in past days (a Preview), the read starts filling missing days in the background and a later call may return more. Use metricType to pick the family and horizon to pick the cycle-time window. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
707
+ inputSchema: {
708
+ type: "object",
709
+ properties: {
710
+ ...idInputSchema.properties,
711
+ ...dateRangeProperties,
712
+ ...percentilesOverTimeProperties
713
+ },
714
+ required: ["id"],
715
+ additionalProperties: false
716
+ }
717
+ },
718
+ {
719
+ name: "lighthouse_portfolio_metrics_percentilesOverTime",
720
+ description: "Get the percentiles-over-time trend for a portfolio by ID: the p50/p70/p85/p95 quartet recorded on each captured day, optionally filtered by start and end dates. By default Lighthouse only returns days it recorded, so a recently upgraded server returns an empty series until it has recorded some; where a System Admin has switched on filling in past days (a Preview), the read starts filling missing days in the background and a later call may return more. Use metricType to pick the family and horizon to pick the cycle-time window. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
721
+ inputSchema: {
722
+ type: "object",
723
+ properties: {
724
+ ...idInputSchema.properties,
725
+ ...dateRangeProperties,
726
+ ...percentilesOverTimeProperties
727
+ },
728
+ required: ["id"],
729
+ additionalProperties: false
730
+ }
731
+ },
732
+ {
733
+ name: "lighthouse_team_metrics_processBehaviorOverTime",
734
+ description: "Get the process-behaviour-limits-over-time trend for a team by ID: the upper limit, average and lower limit (UNPL/Average/LNPL) recorded on each captured day, optionally filtered by start and end dates. Days are recorded on refresh (and, where a System Admin has switched on filling in past days, filled in the background after a read), and days without a usable baseline are absent rather than zeroed — an empty series means nothing was recorded, never a process pinned at zero. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
735
+ inputSchema: {
736
+ type: "object",
737
+ properties: {
738
+ ...idInputSchema.properties,
739
+ ...dateRangeProperties,
740
+ ...processBehaviorOverTimeProperties
741
+ },
742
+ required: ["id"],
743
+ additionalProperties: false
744
+ }
745
+ },
746
+ {
747
+ name: "lighthouse_portfolio_metrics_processBehaviorOverTime",
748
+ description: "Get the process-behaviour-limits-over-time trend for a portfolio by ID: the upper limit, average and lower limit (UNPL/Average/LNPL) recorded on each captured day, optionally filtered by start and end dates. Days are recorded on refresh (and, where a System Admin has switched on filling in past days, filled in the background after a read), and days without a usable baseline are absent rather than zeroed — an empty series means nothing was recorded, never a process pinned at zero. FeatureSize is available here and not on teams. A second text block, `summary: …`, states the answer as the dashboard does, in the instance's terminology: the heading and its sentence; the first block is the facts, unchanged.",
749
+ inputSchema: {
750
+ type: "object",
751
+ properties: {
752
+ ...idInputSchema.properties,
753
+ ...dateRangeProperties,
754
+ ...processBehaviorOverTimeProperties
755
+ },
756
+ required: ["id"],
757
+ additionalProperties: false
758
+ }
759
+ },
760
+ {
761
+ name: "lighthouse_portfolio_metrics_throughput",
762
+ description: "Get throughput run-chart data for a portfolio by ID, optionally filtered by start and end dates. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and its sentence.",
763
+ inputSchema: {
764
+ type: "object",
765
+ properties: {
766
+ ...idInputSchema.properties,
767
+ ...dateRangeProperties
768
+ },
769
+ required: ["id"],
770
+ additionalProperties: false
771
+ }
772
+ },
773
+ {
774
+ name: "lighthouse_team_metrics_workItemAge",
775
+ description: "Get per-item work item age over time for a team by ID. Returns daily snapshots with each in-progress item's age in days derived from its startedDate. Items without a startedDate are omitted. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and its sentence.",
776
+ inputSchema: {
777
+ type: "object",
778
+ properties: {
779
+ ...idInputSchema.properties,
780
+ ...dateRangeProperties
781
+ },
782
+ required: ["id"],
783
+ additionalProperties: false
784
+ }
785
+ },
786
+ {
787
+ name: "lighthouse_team_metrics_totalWorkItemAge",
788
+ description: "Get the total (summed) work item age over time for a team by ID. Returns daily totals of all in-progress item ages derived from startedDate. Items without a startedDate are not counted. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and its sentence.",
789
+ inputSchema: {
790
+ type: "object",
791
+ properties: {
792
+ ...idInputSchema.properties,
793
+ ...dateRangeProperties
794
+ },
795
+ required: ["id"],
796
+ additionalProperties: false
797
+ }
798
+ },
799
+ {
800
+ name: "lighthouse_portfolio_metrics_workItemAge",
801
+ description: "Get per-item work item age over time for a portfolio by ID. Returns daily snapshots with each in-progress item's age in days derived from its startedDate. Items without a startedDate are omitted. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and its sentence.",
802
+ inputSchema: {
803
+ type: "object",
804
+ properties: {
805
+ ...idInputSchema.properties,
806
+ ...dateRangeProperties
807
+ },
808
+ required: ["id"],
809
+ additionalProperties: false
810
+ }
811
+ },
812
+ {
813
+ name: "lighthouse_portfolio_metrics_totalWorkItemAge",
814
+ description: "Get the total (summed) work item age over time for a portfolio by ID. Returns daily totals of all in-progress item ages derived from startedDate. Items without a startedDate are not counted. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and its sentence.",
815
+ inputSchema: {
816
+ type: "object",
817
+ properties: {
818
+ ...idInputSchema.properties,
819
+ ...dateRangeProperties
820
+ },
821
+ required: ["id"],
822
+ additionalProperties: false
823
+ }
824
+ },
825
+ {
826
+ name: "lighthouse_feature_get",
827
+ description: "Get feature details by numeric IDs or external reference IDs. A second text block, `summary`, counts them in the instance's terminology; the first block is the facts, unchanged.",
828
+ inputSchema: {
829
+ type: "object",
830
+ properties: {
831
+ ids: {
832
+ type: "array",
833
+ items: { type: "integer" },
834
+ description: "Feature IDs."
835
+ },
836
+ refs: {
837
+ type: "array",
838
+ items: { type: "string" },
839
+ description: "Feature reference identifiers."
840
+ }
841
+ },
842
+ additionalProperties: false
843
+ }
844
+ },
845
+ {
846
+ name: "lighthouse_feature_workitems",
847
+ description: "Get work items linked to a feature by ID. A second text block, `summary`, heads them as lh does: the feature and how many work items it holds, in the instance's terminology; the first block is the facts, unchanged.",
848
+ inputSchema: idInputSchema
849
+ },
850
+ {
851
+ name: "lighthouse_delivery_list",
852
+ description: "List deliveries for a portfolio by portfolio ID. A second text block, `summary`, counts them in the instance's terminology; the first block is the facts, unchanged.",
853
+ inputSchema: idInputSchema
854
+ },
855
+ {
856
+ name: "lighthouse_delivery_metrics",
857
+ description: "Get a delivery's recorded trend by delivery ID: one row per day with total, done and remaining work, the epic count and the likelihood. Set detail to \"epics\" for the per-epic breakdown and the forecast distribution, which are far larger. Forward-only, so it starts at the first recorded snapshot. `summary` states the delivery and its date as lh heads them, in the instance's terminology: a second text block beside the rows, a field of the detailed payload. Requires Lighthouse newer than v26.5.29.5.",
858
+ inputSchema: {
859
+ type: "object",
860
+ properties: {
861
+ id: {
862
+ type: "number",
863
+ description: "Delivery ID."
864
+ },
865
+ detail: {
866
+ type: "string",
867
+ enum: ["epics"],
868
+ description: "Omit for one row per day. \"epics\" returns the whole recorded payload."
869
+ }
870
+ },
871
+ required: ["id"],
872
+ additionalProperties: false
873
+ }
874
+ },
875
+ {
876
+ name: "lighthouse_blackout_list",
877
+ description: "List the recurring blackout rules (recurring non-working days excluded from forecasts). Requires Lighthouse newer than v26.5.29.5. A second text block, `summary`, counts them as the settings page does; the first block is the facts, unchanged, each rule's own `summary` field included.",
878
+ inputSchema: emptyInputSchema
879
+ },
880
+ {
881
+ name: "lighthouse_blackout_create",
882
+ description: "Create a recurring blackout rule (weekdays + every-N-weeks interval + start date + optional open end). Premium, system-admin only. Requires Lighthouse newer than v26.5.29.5. A second text block, `summary`, confirms it in one line as lh does; the first block is the facts, unchanged, the rule's own `summary` field included.",
883
+ inputSchema: {
884
+ type: "object",
885
+ properties: { ...recurringBlackoutRuleProperties },
886
+ required: [
887
+ "weekdays",
888
+ "intervalWeeks",
889
+ "start",
890
+ "description"
891
+ ],
892
+ additionalProperties: false
893
+ }
894
+ },
895
+ {
896
+ name: "lighthouse_blackout_update",
897
+ description: "Update a recurring blackout rule by ID. Premium, system-admin only. Requires Lighthouse newer than v26.5.29.5. A second text block, `summary`, confirms it in one line as lh does; the first block is the facts, unchanged, the rule's own `summary` field included.",
898
+ inputSchema: {
899
+ type: "object",
900
+ properties: {
901
+ ...idInputSchema.properties,
902
+ ...recurringBlackoutRuleProperties
903
+ },
904
+ required: [
905
+ "id",
906
+ "weekdays",
907
+ "intervalWeeks",
908
+ "start",
909
+ "description"
910
+ ],
911
+ additionalProperties: false
912
+ }
913
+ },
914
+ {
915
+ name: "lighthouse_blackout_delete",
916
+ description: "Delete a recurring blackout rule by ID. Premium, system-admin only. Requires Lighthouse newer than v26.5.29.5. A second text block, `summary`, confirms it in one line as lh does; the first block is the facts, unchanged.",
917
+ inputSchema: idInputSchema
918
+ },
919
+ {
920
+ name: "lighthouse_forecast_manual",
921
+ description: "Run a manual forecast for a team by ID with optional remaining items and target date. Pass applyFilterOverride=true to apply the team's forecast filter, false to skip it, or omit to respect the team setting (Lighthouse v26.5.24.10+). The response includes filterApplied (boolean) and excludedSummary (string) when a filter was applied. `summary` states the answer as the web does, in the instance's terminology: the heading, then the likelihood sentence when both remaining items and a target date were given.",
922
+ inputSchema: {
923
+ type: "object",
924
+ properties: {
925
+ ...idInputSchema.properties,
926
+ remainingItems: {
927
+ type: "integer",
928
+ description: "Remaining items to forecast."
929
+ },
930
+ targetDate: {
931
+ type: "string",
932
+ description: "Optional target date in ISO format (YYYY-MM-DD)."
933
+ },
934
+ ...forecastFilterOverrideProperty
935
+ },
936
+ required: ["id"],
937
+ additionalProperties: false
938
+ }
939
+ },
940
+ {
941
+ name: "lighthouse_forecast_backtest",
942
+ description: "Run a forecast backtest for a team by ID using forecast and historical date ranges. Pass applyFilterOverride=true to apply the team's forecast filter, false to skip it, or omit to respect the team setting (Lighthouse v26.5.24.10+). The response includes filterApplied (boolean) and excludedSummary (string) when a filter was applied. `summary` states the answer as the web does, in the instance's terminology: the heading, the period with its historical data, and the actual throughput.",
943
+ inputSchema: {
944
+ type: "object",
945
+ properties: {
946
+ ...idInputSchema.properties,
947
+ startDate: {
948
+ type: "string",
949
+ description: "Forecast start date in ISO format (YYYY-MM-DD)."
950
+ },
951
+ endDate: {
952
+ type: "string",
953
+ description: "Forecast end date in ISO format (YYYY-MM-DD)."
954
+ },
955
+ historicalStartDate: {
956
+ type: "string",
957
+ description: "Historical sample start date in ISO format (YYYY-MM-DD)."
958
+ },
959
+ historicalEndDate: {
960
+ type: "string",
961
+ description: "Historical sample end date in ISO format (YYYY-MM-DD)."
962
+ },
963
+ ...forecastFilterOverrideProperty
964
+ },
965
+ required: [
966
+ "id",
967
+ "startDate",
968
+ "endDate",
969
+ "historicalStartDate",
970
+ "historicalEndDate"
971
+ ],
972
+ additionalProperties: false
973
+ }
974
+ },
975
+ {
976
+ name: "lighthouse_team_metrics_cumulativeStateTime",
977
+ description: "Get cumulative time-per-state bar data for a team by ID: one entry per Doing-category workflow state with total/completed/ongoing contribution days and item counts. Optionally filter by date range and a subset of work-item IDs. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and its sentence.",
978
+ inputSchema: {
979
+ type: "object",
980
+ properties: {
981
+ ...idInputSchema.properties,
982
+ ...dateRangeProperties,
983
+ ...itemIdsProperty
984
+ },
985
+ required: ["id"],
986
+ additionalProperties: false
987
+ }
988
+ },
989
+ {
990
+ name: "lighthouse_team_metrics_cumulativeStateTimeItems",
991
+ description: "Get the per-item drill-down for ONE state of a team's cumulative time-per-state chart: the work items that contributed to that state, with days contributed. Requires the state name. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and the drill-down's title.",
992
+ inputSchema: {
993
+ type: "object",
994
+ properties: {
995
+ ...idInputSchema.properties,
996
+ ...cumulativeStateProperty,
997
+ ...dateRangeProperties,
998
+ ...itemIdsProperty
999
+ },
1000
+ required: ["id", "state"],
1001
+ additionalProperties: false
1002
+ }
1003
+ },
1004
+ {
1005
+ name: "lighthouse_team_metrics_cumulativeStateTimeCandidates",
1006
+ description: "List the work items selectable in a team's cumulative time-per-state item picker for the window (the in-scope candidate set). Search keys are referenceId and title.",
1007
+ inputSchema: {
1008
+ type: "object",
1009
+ properties: {
1010
+ ...idInputSchema.properties,
1011
+ ...dateRangeProperties
1012
+ },
1013
+ required: ["id"],
1014
+ additionalProperties: false
1015
+ }
1016
+ },
1017
+ {
1018
+ name: "lighthouse_portfolio_metrics_cumulativeStateTime",
1019
+ description: "Get cumulative time-per-state bar data for a portfolio by ID: one entry per Doing-category workflow state with total/completed/ongoing contribution days and item counts. Optionally filter by date range and a subset of work-item IDs. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and its sentence.",
1020
+ inputSchema: {
1021
+ type: "object",
1022
+ properties: {
1023
+ ...idInputSchema.properties,
1024
+ ...dateRangeProperties,
1025
+ ...itemIdsProperty
1026
+ },
1027
+ required: ["id"],
1028
+ additionalProperties: false
1029
+ }
1030
+ },
1031
+ {
1032
+ name: "lighthouse_portfolio_metrics_cumulativeStateTimeItems",
1033
+ description: "Get the per-item drill-down for ONE state of a portfolio's cumulative time-per-state chart: the work items that contributed to that state, with days contributed. Requires the state name. `summary` states the answer as the dashboard does, in the instance's terminology: the heading and the drill-down's title.",
1034
+ inputSchema: {
1035
+ type: "object",
1036
+ properties: {
1037
+ ...idInputSchema.properties,
1038
+ ...cumulativeStateProperty,
1039
+ ...dateRangeProperties,
1040
+ ...itemIdsProperty
1041
+ },
1042
+ required: ["id", "state"],
1043
+ additionalProperties: false
1044
+ }
1045
+ },
1046
+ {
1047
+ name: "lighthouse_portfolio_metrics_cumulativeStateTimeCandidates",
1048
+ description: "List the work items selectable in a portfolio's cumulative time-per-state item picker for the window (the in-scope candidate set). Search keys are referenceId and title.",
1049
+ inputSchema: {
1050
+ type: "object",
1051
+ properties: {
1052
+ ...idInputSchema.properties,
1053
+ ...dateRangeProperties
1054
+ },
1055
+ required: ["id"],
1056
+ additionalProperties: false
1057
+ }
1058
+ },
1059
+ {
1060
+ name: "lighthouse_team_metrics_wip",
1061
+ description: "Get what a team has in progress right now by ID: each work item in progress today with its age (workItemAge), state, whether it is blocked (isBlocked) and since when (blockedSince), and its link (url). Use it for what is blocked or aging today; for how many were blocked on past days use lighthouse_team_metrics_blockedCountHistory. A second text block, `summary: …`, states the answer as lh does, in the instance's terminology: the heading, how many are in progress against the System WIP Limit, and how many are blocked; the first block is the facts, unchanged.",
1062
+ inputSchema: idInputSchema
1063
+ },
1064
+ {
1065
+ name: "lighthouse_team_metrics_sleRisk",
1066
+ description: "Get how likely each work item a team has in progress is to miss the team's SLE, by team ID: Lighthouse's own risk per work item (referenceId, risk in percent), with the finished work items that reached the same age (finishedItemsStillOpenAtThisAge) and how many of those went on to miss (finishedItemsThatWentOnToMiss, null when the work item is already past the SLE). A work item counts as at risk from 70%. Use it for \"which work items will miss our SLE\" and \"what should we swarm on\"; never compute a risk yourself. Needs a Lighthouse newer than v26.9.19.10. A second text block, `summary: …`, states the answer as lh does, in the instance's terminology: the heading, how many are at risk against the SLE, then every work item highest risk first with its name and age; the first block is the facts, unchanged.",
1067
+ inputSchema: idInputSchema
1068
+ },
1069
+ {
1070
+ name: "lighthouse_team_metrics_processBehaviorChart",
1071
+ description: "Get one Process Behaviour Chart of a team by ID, as Lighthouse computes it: its status (and statusReason when it is not Ready), the average and natural process limits, whether a baseline is configured, and every point (xValue, yValue) with the signals Lighthouse found on it (specialCauses: LargeChange, ModerateChange, ModerateShift, SmallShift; None means no signal) and whether it falls on a blackout day (isBlackout). metricType picks the chart: Throughput, Arrivals, Wip, WorkItemAge (the Total Work Item Age chart) or CycleTime; a team has no FeatureSize chart. A point is a day, except on CycleTime, which has one point per finished work item, dated when it finished, so several can share a day. Use it for \"is something unusual going on\" and \"did our process change\"; never work a signal out yourself. Optionally filtered by start and end dates. A second text block, `summary: …`, states the answer as lh does, in the instance's terminology: the heading, the chart's title, and each signal with the days it fired, or that there are no signals; the facts are unchanged.",
1072
+ inputSchema: chartInputSchema(TEAM_CHART_TYPES)
1073
+ },
1074
+ {
1075
+ name: "lighthouse_portfolio_metrics_processBehaviorChart",
1076
+ description: "Get one Process Behaviour Chart of a portfolio by ID, as Lighthouse computes it: its status (and statusReason when it is not Ready), the average and natural process limits, whether a baseline is configured, and every point (xValue, yValue) with the signals Lighthouse found on it (specialCauses: LargeChange, ModerateChange, ModerateShift, SmallShift; None means no signal) and whether it falls on a blackout day (isBlackout). metricType picks the chart: Throughput, Arrivals, Wip, WorkItemAge (the Total Work Item Age chart), CycleTime or FeatureSize. A point is a day, except on CycleTime, which has one point per finished work item, and FeatureSize, which has one per finished feature, each dated when it finished, so several can share a day. Use it for \"is something unusual going on\" and \"did our process change\"; never work a signal out yourself. Optionally filtered by start and end dates. A second text block, `summary: …`, states the answer as lh does, in the instance's terminology: the heading, the chart's title, and each signal with the days it fired, or that there are no signals; the facts are unchanged.",
1077
+ inputSchema: chartInputSchema(processBehaviorMetricTypes)
1078
+ }
724
1079
  ];
725
- var getProcessBehaviorMetricType = (argumentsPayload) => {
726
- const raw = getStringArgument(argumentsPayload, "metricType");
727
- return processBehaviorMetricTypes.find((candidate) => candidate === raw);
728
- };
729
- var getHorizonArgument = (argumentsPayload) => {
730
- if (!isObjectRecord(argumentsPayload)) {
731
- return void 0;
732
- }
733
- const horizon = argumentsPayload.horizon;
734
- return typeof horizon === "number" && Number.isInteger(horizon) ? horizon : void 0;
735
- };
736
- var getApplyFilterOverride = (argumentsPayload) => {
737
- if (!isObjectRecord(argumentsPayload)) {
738
- return void 0;
739
- }
740
- const value = argumentsPayload.applyFilterOverride;
741
- return typeof value === "boolean" ? value : void 0;
742
- };
743
- var isoDateStringSchema = import_zod.z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected ISO date in YYYY-MM-DD format.");
744
- var weekdaySchema = import_zod.z.enum([
745
- "Sunday",
746
- "Monday",
747
- "Tuesday",
748
- "Wednesday",
749
- "Thursday",
750
- "Friday",
751
- "Saturday"
752
- ]);
753
- var recurringBlackoutRuleInputSchema = import_zod.z.object({
754
- weekdays: import_zod.z.array(weekdaySchema),
755
- intervalWeeks: import_zod.z.number().int(),
756
- start: isoDateStringSchema,
757
- end: isoDateStringSchema.nullable().optional(),
758
- description: import_zod.z.string()
1080
+ const getDefinitionId = (argumentsPayload) => {
1081
+ if (typeof argumentsPayload !== "object" || argumentsPayload === null || Array.isArray(argumentsPayload)) return;
1082
+ const value = argumentsPayload.definitionId;
1083
+ return typeof value === "number" && Number.isInteger(value) ? value : void 0;
1084
+ };
1085
+ const linesOf = (...lines) => lines.filter((line) => line !== null).join("\n");
1086
+ const readTeamWording = (client, teamId) => (0, _letpeoplework_lighthouse_client.readAnswerWording)(client, {
1087
+ term: "team",
1088
+ id: teamId,
1089
+ read: () => client.getTeam(teamId)
759
1090
  });
760
- var toolInputSchemas = {
761
- lighthouse_health_check: import_zod.z.object({}),
762
- lighthouse_version_get: import_zod.z.object({}),
763
- lighthouse_worktracking_list: import_zod.z.object({}),
764
- lighthouse_worktracking_get: import_zod.z.object({ id: import_zod.z.number().int() }),
765
- lighthouse_team_list: import_zod.z.object({}),
766
- lighthouse_team_get: import_zod.z.object({ id: import_zod.z.number().int() }),
767
- lighthouse_team_refresh: import_zod.z.object({ id: import_zod.z.number().int() }),
768
- lighthouse_portfolio_list: import_zod.z.object({}),
769
- lighthouse_portfolio_get: import_zod.z.object({ id: import_zod.z.number().int() }),
770
- lighthouse_portfolio_refresh: import_zod.z.object({ id: import_zod.z.number().int() }),
771
- lighthouse_team_metrics_throughput: import_zod.z.object({
772
- id: import_zod.z.number().int(),
773
- startDate: isoDateStringSchema.optional(),
774
- endDate: isoDateStringSchema.optional(),
775
- view: import_zod.z.enum(["raw", "filtered"]).optional()
776
- }),
777
- lighthouse_team_metrics_cycleTimePercentiles: import_zod.z.object({
778
- id: import_zod.z.number().int(),
779
- startDate: isoDateStringSchema.optional(),
780
- endDate: isoDateStringSchema.optional(),
781
- definitionId: import_zod.z.number().int().optional()
782
- }),
783
- lighthouse_team_metrics_workItemAgePercentiles: import_zod.z.object({
784
- id: import_zod.z.number().int(),
785
- startDate: isoDateStringSchema.optional(),
786
- endDate: isoDateStringSchema.optional()
787
- }),
788
- lighthouse_portfolio_metrics_workItemAgePercentiles: import_zod.z.object({
789
- id: import_zod.z.number().int(),
790
- startDate: isoDateStringSchema.optional(),
791
- endDate: isoDateStringSchema.optional()
792
- }),
793
- lighthouse_team_metrics_blockedCountHistory: import_zod.z.object({
794
- id: import_zod.z.number().int(),
795
- startDate: isoDateStringSchema.optional(),
796
- endDate: isoDateStringSchema.optional()
797
- }),
798
- lighthouse_portfolio_metrics_blockedCountHistory: import_zod.z.object({
799
- id: import_zod.z.number().int(),
800
- startDate: isoDateStringSchema.optional(),
801
- endDate: isoDateStringSchema.optional()
802
- }),
803
- lighthouse_team_metrics_percentilesOverTime: import_zod.z.object({
804
- id: import_zod.z.number().int(),
805
- startDate: isoDateStringSchema.optional(),
806
- endDate: isoDateStringSchema.optional(),
807
- metricType: import_zod.z.enum(["CycleTime", "WorkItemAge"]).optional(),
808
- horizon: import_zod.z.number().int().optional()
809
- }),
810
- lighthouse_portfolio_metrics_percentilesOverTime: import_zod.z.object({
811
- id: import_zod.z.number().int(),
812
- startDate: isoDateStringSchema.optional(),
813
- endDate: isoDateStringSchema.optional(),
814
- metricType: import_zod.z.enum(["CycleTime", "WorkItemAge"]).optional(),
815
- horizon: import_zod.z.number().int().optional()
816
- }),
817
- lighthouse_team_metrics_processBehaviorOverTime: import_zod.z.object({
818
- id: import_zod.z.number().int(),
819
- startDate: isoDateStringSchema.optional(),
820
- endDate: isoDateStringSchema.optional(),
821
- metricType: import_zod.z.enum([
822
- "Throughput",
823
- "WorkItemAge",
824
- "Wip",
825
- "CycleTime",
826
- "Arrivals",
827
- "FeatureSize"
828
- ]).optional()
829
- }),
830
- lighthouse_portfolio_metrics_processBehaviorOverTime: import_zod.z.object({
831
- id: import_zod.z.number().int(),
832
- startDate: isoDateStringSchema.optional(),
833
- endDate: isoDateStringSchema.optional(),
834
- metricType: import_zod.z.enum([
835
- "Throughput",
836
- "WorkItemAge",
837
- "Wip",
838
- "CycleTime",
839
- "Arrivals",
840
- "FeatureSize"
841
- ]).optional()
842
- }),
843
- lighthouse_team_metrics_workItemAge: import_zod.z.object({
844
- id: import_zod.z.number().int(),
845
- startDate: isoDateStringSchema.optional(),
846
- endDate: isoDateStringSchema.optional()
847
- }),
848
- lighthouse_team_metrics_totalWorkItemAge: import_zod.z.object({
849
- id: import_zod.z.number().int(),
850
- startDate: isoDateStringSchema.optional(),
851
- endDate: isoDateStringSchema.optional()
852
- }),
853
- lighthouse_portfolio_metrics_throughput: import_zod.z.object({
854
- id: import_zod.z.number().int(),
855
- startDate: isoDateStringSchema.optional(),
856
- endDate: isoDateStringSchema.optional()
857
- }),
858
- lighthouse_portfolio_metrics_workItemAge: import_zod.z.object({
859
- id: import_zod.z.number().int(),
860
- startDate: isoDateStringSchema.optional(),
861
- endDate: isoDateStringSchema.optional()
862
- }),
863
- lighthouse_portfolio_metrics_totalWorkItemAge: import_zod.z.object({
864
- id: import_zod.z.number().int(),
865
- startDate: isoDateStringSchema.optional(),
866
- endDate: isoDateStringSchema.optional()
867
- }),
868
- lighthouse_team_metrics_cumulativeStateTime: import_zod.z.object({
869
- id: import_zod.z.number().int(),
870
- startDate: isoDateStringSchema.optional(),
871
- endDate: isoDateStringSchema.optional(),
872
- itemIds: import_zod.z.array(import_zod.z.number().int()).optional()
873
- }),
874
- lighthouse_team_metrics_cumulativeStateTimeItems: import_zod.z.object({
875
- id: import_zod.z.number().int(),
876
- state: import_zod.z.string(),
877
- startDate: isoDateStringSchema.optional(),
878
- endDate: isoDateStringSchema.optional(),
879
- itemIds: import_zod.z.array(import_zod.z.number().int()).optional()
880
- }),
881
- lighthouse_team_metrics_cumulativeStateTimeCandidates: import_zod.z.object({
882
- id: import_zod.z.number().int(),
883
- startDate: isoDateStringSchema.optional(),
884
- endDate: isoDateStringSchema.optional()
885
- }),
886
- lighthouse_portfolio_metrics_cumulativeStateTime: import_zod.z.object({
887
- id: import_zod.z.number().int(),
888
- startDate: isoDateStringSchema.optional(),
889
- endDate: isoDateStringSchema.optional(),
890
- itemIds: import_zod.z.array(import_zod.z.number().int()).optional()
891
- }),
892
- lighthouse_portfolio_metrics_cumulativeStateTimeItems: import_zod.z.object({
893
- id: import_zod.z.number().int(),
894
- state: import_zod.z.string(),
895
- startDate: isoDateStringSchema.optional(),
896
- endDate: isoDateStringSchema.optional(),
897
- itemIds: import_zod.z.array(import_zod.z.number().int()).optional()
898
- }),
899
- lighthouse_portfolio_metrics_cumulativeStateTimeCandidates: import_zod.z.object({
900
- id: import_zod.z.number().int(),
901
- startDate: isoDateStringSchema.optional(),
902
- endDate: isoDateStringSchema.optional()
903
- }),
904
- lighthouse_feature_get: import_zod.z.object({
905
- ids: import_zod.z.array(import_zod.z.number().int()).optional(),
906
- refs: import_zod.z.array(import_zod.z.string()).optional()
907
- }),
908
- lighthouse_feature_workitems: import_zod.z.object({ id: import_zod.z.number().int() }),
909
- lighthouse_delivery_list: import_zod.z.object({ id: import_zod.z.number().int() }),
910
- lighthouse_delivery_metrics: import_zod.z.object({
911
- id: import_zod.z.number().int(),
912
- detail: import_zod.z.literal("epics").optional()
913
- }),
914
- lighthouse_blackout_list: import_zod.z.object({}),
915
- lighthouse_blackout_create: recurringBlackoutRuleInputSchema,
916
- lighthouse_blackout_update: recurringBlackoutRuleInputSchema.extend({
917
- id: import_zod.z.number().int()
918
- }),
919
- lighthouse_blackout_delete: import_zod.z.object({ id: import_zod.z.number().int() }),
920
- lighthouse_forecast_manual: import_zod.z.object({
921
- id: import_zod.z.number().int(),
922
- remainingItems: import_zod.z.number().int().optional(),
923
- targetDate: isoDateStringSchema.optional(),
924
- applyFilterOverride: import_zod.z.boolean().optional()
925
- }),
926
- lighthouse_forecast_backtest: import_zod.z.object({
927
- id: import_zod.z.number().int(),
928
- startDate: isoDateStringSchema,
929
- endDate: isoDateStringSchema,
930
- historicalStartDate: isoDateStringSchema,
931
- historicalEndDate: isoDateStringSchema,
932
- applyFilterOverride: import_zod.z.boolean().optional()
933
- })
934
- };
935
- var isReadOnlyTool = (toolName) => !(toolName.endsWith("_refresh") || toolName.endsWith("_create") || toolName.endsWith("_update") || toolName.endsWith("_delete"));
936
- var createMcpCoreRuntime = (dependencies) => ({
937
- listTools: () => toolDefinitions,
938
- callTool: async (name, argumentsPayload) => {
939
- const client = dependencies.createClient();
940
- if (name === "lighthouse_health_check") {
941
- const health = await client.checkConnectivity();
942
- if (health.category === "success") {
943
- return getSuccessToolResult("connectivity: success");
944
- }
945
- const healthReasonSuffix = health.reason === void 0 ? "" : ` (${health.reason})`;
946
- return getErrorToolResult(
947
- `connectivity: ${health.category}${healthReasonSuffix}`
948
- );
949
- }
950
- if (name === "lighthouse_version_get") {
951
- const version = await client.getVersion();
952
- if (version.ok) {
953
- return getSuccessToolResult(`version: ${version.value}`);
954
- }
955
- return getErrorToolResult(
956
- `version: ${version.error.category} (${version.error.reason})`
957
- );
958
- }
959
- if (name === "lighthouse_worktracking_list") {
960
- const connections = await client.listWorkTrackingConnections();
961
- if (connections.ok) {
962
- return getSuccessToolResult(
963
- `worktracking: ${encodePayload(connections.value)}`
964
- );
965
- }
966
- return getErrorToolResult(
967
- `worktracking: ${connections.error.category} (${connections.error.reason})`
968
- );
969
- }
970
- if (name === "lighthouse_worktracking_get") {
971
- const id = getNumericId(argumentsPayload);
972
- if (id === null) {
973
- return getErrorToolResult("worktracking: invalid id");
974
- }
975
- const connection = await client.getWorkTrackingConnection(id);
976
- if (connection.ok) {
977
- return getSuccessToolResult(
978
- `worktracking: ${encodePayload(connection.value)}`
979
- );
980
- }
981
- return getErrorToolResult(
982
- `worktracking: ${connection.error.category} (${connection.error.reason})`
983
- );
984
- }
985
- if (name === "lighthouse_team_list") {
986
- const teams = await client.listTeams();
987
- if (teams.ok) {
988
- return getSuccessToolResult(`teams: ${encodePayload(teams.value)}`);
989
- }
990
- return getErrorToolResult(
991
- `teams: ${teams.error.category} (${teams.error.reason})`
992
- );
993
- }
994
- if (name === "lighthouse_team_get") {
995
- const id = getNumericId(argumentsPayload);
996
- if (id === null) {
997
- return getErrorToolResult("team: invalid id");
998
- }
999
- const team = await client.getTeam(id);
1000
- if (team.ok) {
1001
- return getSuccessToolResult(`team: ${encodePayload(team.value)}`);
1002
- }
1003
- return getErrorToolResult(
1004
- `team: ${team.error.category} (${team.error.reason})`
1005
- );
1006
- }
1007
- if (name === "lighthouse_team_refresh") {
1008
- const id = getNumericId(argumentsPayload);
1009
- if (id === null) {
1010
- return getErrorToolResult("team: invalid id");
1011
- }
1012
- const result = await client.refreshTeam(id);
1013
- if (result.ok) {
1014
- return getSuccessToolResult(`team refreshed: ${id}`);
1015
- }
1016
- return getErrorToolResult(
1017
- `team refresh: ${result.error.category} (${result.error.reason})`
1018
- );
1019
- }
1020
- if (name === "lighthouse_portfolio_list") {
1021
- const portfolios = await client.listPortfolios();
1022
- if (portfolios.ok) {
1023
- return getSuccessToolResult(
1024
- `portfolios: ${encodePayload(portfolios.value)}`
1025
- );
1026
- }
1027
- return getErrorToolResult(
1028
- `portfolios: ${portfolios.error.category} (${portfolios.error.reason})`
1029
- );
1030
- }
1031
- if (name === "lighthouse_portfolio_get") {
1032
- const id = getNumericId(argumentsPayload);
1033
- if (id === null) {
1034
- return getErrorToolResult("portfolio: invalid id");
1035
- }
1036
- const portfolio = await client.getPortfolio(id);
1037
- if (portfolio.ok) {
1038
- return getSuccessToolResult(
1039
- `portfolio: ${encodePayload(portfolio.value)}`
1040
- );
1041
- }
1042
- return getErrorToolResult(
1043
- `portfolio: ${portfolio.error.category} (${portfolio.error.reason})`
1044
- );
1045
- }
1046
- if (name === "lighthouse_portfolio_refresh") {
1047
- const id = getNumericId(argumentsPayload);
1048
- if (id === null) {
1049
- return getErrorToolResult("portfolio: invalid id");
1050
- }
1051
- const result = await client.refreshPortfolio(id);
1052
- if (result.ok) {
1053
- return getSuccessToolResult(`portfolio refreshed: ${id}`);
1054
- }
1055
- return getErrorToolResult(
1056
- `portfolio refresh: ${result.error.category} (${result.error.reason})`
1057
- );
1058
- }
1059
- if (name === "lighthouse_team_metrics_throughput") {
1060
- const id = getNumericId(argumentsPayload);
1061
- if (id === null) {
1062
- return getErrorToolResult("team metrics: invalid id");
1063
- }
1064
- const range = getDateRange(argumentsPayload);
1065
- const view = getThroughputFilterView(argumentsPayload);
1066
- const result = await client.getTeamThroughput(id, range, view);
1067
- if (result.ok) {
1068
- return getSuccessToolResult(
1069
- `team throughput: ${encodePayload(result.value)}`
1070
- );
1071
- }
1072
- return getErrorToolResult(
1073
- `team metrics: ${result.error.category} (${result.error.reason})`
1074
- );
1075
- }
1076
- if (name === "lighthouse_team_metrics_cycleTimePercentiles") {
1077
- const id = getNumericId(argumentsPayload);
1078
- if (id === null) {
1079
- return getErrorToolResult("team metrics: invalid id");
1080
- }
1081
- const range = getDateRange(argumentsPayload);
1082
- const definitionId = getDefinitionId(argumentsPayload);
1083
- const result = await client.getTeamCycleTimePercentiles(
1084
- id,
1085
- range,
1086
- definitionId
1087
- );
1088
- if (result.ok) {
1089
- return getSuccessToolResult(
1090
- `team cycleTimePercentiles: ${encodePayload(result.value)}`
1091
- );
1092
- }
1093
- return getErrorToolResult(
1094
- `team metrics: ${result.error.category} (${result.error.reason})`
1095
- );
1096
- }
1097
- if (name === "lighthouse_team_metrics_workItemAgePercentiles") {
1098
- const id = getNumericId(argumentsPayload);
1099
- if (id === null) {
1100
- return getErrorToolResult("team metrics: invalid id");
1101
- }
1102
- const range = getDateRange(argumentsPayload);
1103
- const result = await client.getTeamWorkItemAgePercentiles(id, range);
1104
- if (result.ok) {
1105
- return getSuccessToolResult(
1106
- `team workItemAgePercentiles: ${encodePayload(result.value)}`
1107
- );
1108
- }
1109
- return getErrorToolResult(
1110
- `team metrics: ${result.error.category} (${result.error.reason})`
1111
- );
1112
- }
1113
- if (name === "lighthouse_portfolio_metrics_workItemAgePercentiles") {
1114
- const id = getNumericId(argumentsPayload);
1115
- if (id === null) {
1116
- return getErrorToolResult("portfolio metrics: invalid id");
1117
- }
1118
- const range = getDateRange(argumentsPayload);
1119
- const result = await client.getPortfolioWorkItemAgePercentiles(id, range);
1120
- if (result.ok) {
1121
- return getSuccessToolResult(
1122
- `portfolio workItemAgePercentiles: ${encodePayload(result.value)}`
1123
- );
1124
- }
1125
- return getErrorToolResult(
1126
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1127
- );
1128
- }
1129
- if (name === "lighthouse_team_metrics_blockedCountHistory") {
1130
- const id = getNumericId(argumentsPayload);
1131
- if (id === null) {
1132
- return getErrorToolResult("team metrics: invalid id");
1133
- }
1134
- const range = getDateRange(argumentsPayload);
1135
- const result = await client.getTeamBlockedCountHistory(id, range);
1136
- if (result.ok) {
1137
- return getSuccessToolResult(
1138
- `team blockedCountHistory: ${encodePayload(result.value)}`
1139
- );
1140
- }
1141
- return getErrorToolResult(
1142
- `team metrics: ${result.error.category} (${result.error.reason})`
1143
- );
1144
- }
1145
- if (name === "lighthouse_portfolio_metrics_blockedCountHistory") {
1146
- const id = getNumericId(argumentsPayload);
1147
- if (id === null) {
1148
- return getErrorToolResult("portfolio metrics: invalid id");
1149
- }
1150
- const range = getDateRange(argumentsPayload);
1151
- const result = await client.getPortfolioBlockedCountHistory(id, range);
1152
- if (result.ok) {
1153
- return getSuccessToolResult(
1154
- `portfolio blockedCountHistory: ${encodePayload(result.value)}`
1155
- );
1156
- }
1157
- return getErrorToolResult(
1158
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1159
- );
1160
- }
1161
- if (name === "lighthouse_team_metrics_percentilesOverTime") {
1162
- const id = getNumericId(argumentsPayload);
1163
- if (id === null) {
1164
- return getErrorToolResult("team metrics: invalid id");
1165
- }
1166
- const range = getDateRange(argumentsPayload);
1167
- const result = await client.getTeamPercentilesOverTime(
1168
- id,
1169
- range,
1170
- getPercentilesOverTimeMetricType(argumentsPayload),
1171
- getHorizonArgument(argumentsPayload)
1172
- );
1173
- if (result.ok) {
1174
- return getSuccessToolResult(
1175
- `team percentilesOverTime: ${encodePayload(result.value)}`
1176
- );
1177
- }
1178
- return getErrorToolResult(
1179
- `team metrics: ${result.error.category} (${result.error.reason})`
1180
- );
1181
- }
1182
- if (name === "lighthouse_portfolio_metrics_percentilesOverTime") {
1183
- const id = getNumericId(argumentsPayload);
1184
- if (id === null) {
1185
- return getErrorToolResult("portfolio metrics: invalid id");
1186
- }
1187
- const range = getDateRange(argumentsPayload);
1188
- const result = await client.getPortfolioPercentilesOverTime(
1189
- id,
1190
- range,
1191
- getPercentilesOverTimeMetricType(argumentsPayload),
1192
- getHorizonArgument(argumentsPayload)
1193
- );
1194
- if (result.ok) {
1195
- return getSuccessToolResult(
1196
- `portfolio percentilesOverTime: ${encodePayload(result.value)}`
1197
- );
1198
- }
1199
- return getErrorToolResult(
1200
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1201
- );
1202
- }
1203
- if (name === "lighthouse_team_metrics_processBehaviorOverTime") {
1204
- const id = getNumericId(argumentsPayload);
1205
- if (id === null) {
1206
- return getErrorToolResult("team metrics: invalid id");
1207
- }
1208
- const range = getDateRange(argumentsPayload);
1209
- const result = await client.getTeamProcessBehaviorOverTime(
1210
- id,
1211
- range,
1212
- getProcessBehaviorMetricType(argumentsPayload)
1213
- );
1214
- if (result.ok) {
1215
- return getSuccessToolResult(
1216
- `team processBehaviorOverTime: ${encodePayload(result.value)}`
1217
- );
1218
- }
1219
- return getErrorToolResult(
1220
- `team metrics: ${result.error.category} (${result.error.reason})`
1221
- );
1222
- }
1223
- if (name === "lighthouse_portfolio_metrics_processBehaviorOverTime") {
1224
- const id = getNumericId(argumentsPayload);
1225
- if (id === null) {
1226
- return getErrorToolResult("portfolio metrics: invalid id");
1227
- }
1228
- const range = getDateRange(argumentsPayload);
1229
- const result = await client.getPortfolioProcessBehaviorOverTime(
1230
- id,
1231
- range,
1232
- getProcessBehaviorMetricType(argumentsPayload)
1233
- );
1234
- if (result.ok) {
1235
- return getSuccessToolResult(
1236
- `portfolio processBehaviorOverTime: ${encodePayload(result.value)}`
1237
- );
1238
- }
1239
- return getErrorToolResult(
1240
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1241
- );
1242
- }
1243
- if (name === "lighthouse_team_metrics_cumulativeStateTime") {
1244
- const id = getNumericId(argumentsPayload);
1245
- if (id === null) {
1246
- return getErrorToolResult("team metrics: invalid id");
1247
- }
1248
- const result = await client.getTeamCumulativeStateTime(
1249
- id,
1250
- getDateRange(argumentsPayload),
1251
- getItemIdsArgument(argumentsPayload)
1252
- );
1253
- if (result.ok) {
1254
- return getSuccessToolResult(
1255
- `team cumulativeStateTime: ${encodePayload(result.value)}`
1256
- );
1257
- }
1258
- return getErrorToolResult(
1259
- `team metrics: ${result.error.category} (${result.error.reason})`
1260
- );
1261
- }
1262
- if (name === "lighthouse_team_metrics_cumulativeStateTimeItems") {
1263
- const id = getNumericId(argumentsPayload);
1264
- if (id === null) {
1265
- return getErrorToolResult("team metrics: invalid id");
1266
- }
1267
- const state = getStringArgument(argumentsPayload, "state");
1268
- if (state === void 0) {
1269
- return getErrorToolResult("team metrics: missing state");
1270
- }
1271
- const result = await client.getTeamCumulativeStateTimeItems(
1272
- id,
1273
- state,
1274
- getDateRange(argumentsPayload),
1275
- getItemIdsArgument(argumentsPayload)
1276
- );
1277
- if (result.ok) {
1278
- return getSuccessToolResult(
1279
- `team cumulativeStateTimeItems: ${encodePayload(result.value)}`
1280
- );
1281
- }
1282
- return getErrorToolResult(
1283
- `team metrics: ${result.error.category} (${result.error.reason})`
1284
- );
1285
- }
1286
- if (name === "lighthouse_team_metrics_cumulativeStateTimeCandidates") {
1287
- const id = getNumericId(argumentsPayload);
1288
- if (id === null) {
1289
- return getErrorToolResult("team metrics: invalid id");
1290
- }
1291
- const result = await client.getTeamCumulativeStateTimeCandidates(
1292
- id,
1293
- getDateRange(argumentsPayload)
1294
- );
1295
- if (result.ok) {
1296
- return getSuccessToolResult(
1297
- `team cumulativeStateTimeCandidates: ${encodePayload(result.value)}`
1298
- );
1299
- }
1300
- return getErrorToolResult(
1301
- `team metrics: ${result.error.category} (${result.error.reason})`
1302
- );
1303
- }
1304
- if (name === "lighthouse_portfolio_metrics_cumulativeStateTime") {
1305
- const id = getNumericId(argumentsPayload);
1306
- if (id === null) {
1307
- return getErrorToolResult("portfolio metrics: invalid id");
1308
- }
1309
- const result = await client.getPortfolioCumulativeStateTime(
1310
- id,
1311
- getDateRange(argumentsPayload),
1312
- getItemIdsArgument(argumentsPayload)
1313
- );
1314
- if (result.ok) {
1315
- return getSuccessToolResult(
1316
- `portfolio cumulativeStateTime: ${encodePayload(result.value)}`
1317
- );
1318
- }
1319
- return getErrorToolResult(
1320
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1321
- );
1322
- }
1323
- if (name === "lighthouse_portfolio_metrics_cumulativeStateTimeItems") {
1324
- const id = getNumericId(argumentsPayload);
1325
- if (id === null) {
1326
- return getErrorToolResult("portfolio metrics: invalid id");
1327
- }
1328
- const state = getStringArgument(argumentsPayload, "state");
1329
- if (state === void 0) {
1330
- return getErrorToolResult("portfolio metrics: missing state");
1331
- }
1332
- const result = await client.getPortfolioCumulativeStateTimeItems(
1333
- id,
1334
- state,
1335
- getDateRange(argumentsPayload),
1336
- getItemIdsArgument(argumentsPayload)
1337
- );
1338
- if (result.ok) {
1339
- return getSuccessToolResult(
1340
- `portfolio cumulativeStateTimeItems: ${encodePayload(result.value)}`
1341
- );
1342
- }
1343
- return getErrorToolResult(
1344
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1345
- );
1346
- }
1347
- if (name === "lighthouse_portfolio_metrics_cumulativeStateTimeCandidates") {
1348
- const id = getNumericId(argumentsPayload);
1349
- if (id === null) {
1350
- return getErrorToolResult("portfolio metrics: invalid id");
1351
- }
1352
- const result = await client.getPortfolioCumulativeStateTimeCandidates(
1353
- id,
1354
- getDateRange(argumentsPayload)
1355
- );
1356
- if (result.ok) {
1357
- return getSuccessToolResult(
1358
- `portfolio cumulativeStateTimeCandidates: ${encodePayload(result.value)}`
1359
- );
1360
- }
1361
- return getErrorToolResult(
1362
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1363
- );
1364
- }
1365
- if (name === "lighthouse_portfolio_metrics_throughput") {
1366
- const id = getNumericId(argumentsPayload);
1367
- if (id === null) {
1368
- return getErrorToolResult("portfolio metrics: invalid id");
1369
- }
1370
- const range = getDateRange(argumentsPayload);
1371
- const result = await client.getPortfolioThroughput(id, range);
1372
- if (result.ok) {
1373
- return getSuccessToolResult(
1374
- `portfolio throughput: ${encodePayload(result.value)}`
1375
- );
1376
- }
1377
- return getErrorToolResult(
1378
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1379
- );
1380
- }
1381
- if (name === "lighthouse_team_metrics_workItemAge") {
1382
- const id = getNumericId(argumentsPayload);
1383
- if (id === null) {
1384
- return getErrorToolResult("team metrics: invalid id");
1385
- }
1386
- const range = getDateRange(argumentsPayload);
1387
- const result = await client.getTeamWorkItemAgeOverTime(id, range);
1388
- if (result.ok) {
1389
- return getSuccessToolResult(
1390
- `team workItemAge: ${encodePayload(result.value)}`
1391
- );
1392
- }
1393
- return getErrorToolResult(
1394
- `team metrics: ${result.error.category} (${result.error.reason})`
1395
- );
1396
- }
1397
- if (name === "lighthouse_team_metrics_totalWorkItemAge") {
1398
- const id = getNumericId(argumentsPayload);
1399
- if (id === null) {
1400
- return getErrorToolResult("team metrics: invalid id");
1401
- }
1402
- const range = getDateRange(argumentsPayload);
1403
- const result = await client.getTeamTotalWorkItemAgeOverTime(id, range);
1404
- if (result.ok) {
1405
- return getSuccessToolResult(
1406
- `team totalWorkItemAge: ${encodePayload(result.value)}`
1407
- );
1408
- }
1409
- return getErrorToolResult(
1410
- `team metrics: ${result.error.category} (${result.error.reason})`
1411
- );
1412
- }
1413
- if (name === "lighthouse_portfolio_metrics_workItemAge") {
1414
- const id = getNumericId(argumentsPayload);
1415
- if (id === null) {
1416
- return getErrorToolResult("portfolio metrics: invalid id");
1417
- }
1418
- const range = getDateRange(argumentsPayload);
1419
- const result = await client.getPortfolioWorkItemAgeOverTime(id, range);
1420
- if (result.ok) {
1421
- return getSuccessToolResult(
1422
- `portfolio workItemAge: ${encodePayload(result.value)}`
1423
- );
1424
- }
1425
- return getErrorToolResult(
1426
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1427
- );
1428
- }
1429
- if (name === "lighthouse_portfolio_metrics_totalWorkItemAge") {
1430
- const id = getNumericId(argumentsPayload);
1431
- if (id === null) {
1432
- return getErrorToolResult("portfolio metrics: invalid id");
1433
- }
1434
- const range = getDateRange(argumentsPayload);
1435
- const result = await client.getPortfolioTotalWorkItemAgeOverTime(
1436
- id,
1437
- range
1438
- );
1439
- if (result.ok) {
1440
- return getSuccessToolResult(
1441
- `portfolio totalWorkItemAge: ${encodePayload(result.value)}`
1442
- );
1443
- }
1444
- return getErrorToolResult(
1445
- `portfolio metrics: ${result.error.category} (${result.error.reason})`
1446
- );
1447
- }
1448
- if (name === "lighthouse_feature_get") {
1449
- const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1450
- const idsValue = payload.ids;
1451
- const refsValue = payload.refs;
1452
- if (Array.isArray(idsValue) && idsValue.length > 0) {
1453
- const ids = idsValue.filter((v) => typeof v === "number");
1454
- const result = await client.getFeaturesByIds(ids);
1455
- if (result.ok) {
1456
- return getSuccessToolResult(
1457
- `features: ${encodePayload(result.value)}`
1458
- );
1459
- }
1460
- return getErrorToolResult(
1461
- `features: ${result.error.category} (${result.error.reason})`
1462
- );
1463
- }
1464
- if (Array.isArray(refsValue) && refsValue.length > 0) {
1465
- const refs = refsValue.filter(
1466
- (v) => typeof v === "string"
1467
- );
1468
- const result = await client.getFeaturesByReferences(refs);
1469
- if (result.ok) {
1470
- return getSuccessToolResult(
1471
- `features: ${encodePayload(result.value)}`
1472
- );
1473
- }
1474
- return getErrorToolResult(
1475
- `features: ${result.error.category} (${result.error.reason})`
1476
- );
1477
- }
1478
- return getErrorToolResult(
1479
- "features: provide ids (array of numbers) or refs (array of strings)"
1480
- );
1481
- }
1482
- if (name === "lighthouse_feature_workitems") {
1483
- const id = getNumericId(argumentsPayload);
1484
- if (id === null) {
1485
- return getErrorToolResult("feature workitems: invalid id");
1486
- }
1487
- const result = await client.getFeatureWorkItems(id);
1488
- if (result.ok) {
1489
- return getSuccessToolResult(
1490
- `feature workitems: ${encodePayload(result.value)}`
1491
- );
1492
- }
1493
- return getErrorToolResult(
1494
- `feature workitems: ${result.error.category} (${result.error.reason})`
1495
- );
1496
- }
1497
- if (name === "lighthouse_delivery_list") {
1498
- const id = getNumericId(argumentsPayload);
1499
- if (id === null) {
1500
- return getErrorToolResult(
1501
- "delivery: invalid id (portfolio id required)"
1502
- );
1503
- }
1504
- const result = await client.listDeliveries(id);
1505
- if (result.ok) {
1506
- return getSuccessToolResult(
1507
- `deliveries: ${encodePayload(result.value)}`
1508
- );
1509
- }
1510
- return getErrorToolResult(
1511
- `delivery: ${result.error.category} (${result.error.reason})`
1512
- );
1513
- }
1514
- if (name === "lighthouse_delivery_metrics") {
1515
- const id = getNumericId(argumentsPayload);
1516
- if (id === null) {
1517
- return getErrorToolResult(
1518
- "delivery metrics: invalid id (delivery id required)"
1519
- );
1520
- }
1521
- const wantsEpics = argumentsPayload?.detail === "epics";
1522
- const result = await client.getDeliveryMetricsHistory(id);
1523
- if (!result.ok) {
1524
- return getErrorToolResult(
1525
- `delivery metrics: ${result.error.category} (${result.error.reason})`
1526
- );
1527
- }
1528
- const payload = wantsEpics ? result.value : (0, import_lighthouse_client.summariseDeliveryMetricsHistory)(result.value);
1529
- return getSuccessToolResult(
1530
- `delivery metrics: ${encodePayload(payload)}`
1531
- );
1532
- }
1533
- if (name === "lighthouse_blackout_list") {
1534
- const result = await client.getRecurringBlackoutRules();
1535
- if (result.ok) {
1536
- return getSuccessToolResult(
1537
- `recurringBlackoutRules: ${encodePayload(result.value)}`
1538
- );
1539
- }
1540
- return getErrorToolResult(
1541
- `blackout: ${result.error.category} (${result.error.reason})`
1542
- );
1543
- }
1544
- if (name === "lighthouse_blackout_create") {
1545
- const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1546
- const result = await client.createRecurringBlackoutRule(payload);
1547
- if (result.ok) {
1548
- return getSuccessToolResult(
1549
- `recurringBlackoutRule: ${encodePayload(result.value)}`
1550
- );
1551
- }
1552
- return getErrorToolResult(
1553
- `blackout: ${result.error.category} (${result.error.reason})`
1554
- );
1555
- }
1556
- if (name === "lighthouse_blackout_update") {
1557
- const id = getNumericId(argumentsPayload);
1558
- if (id === null) {
1559
- return getErrorToolResult("blackout: invalid id (rule id required)");
1560
- }
1561
- const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1562
- const { id: _ignoredId, ...rulePayload } = payload;
1563
- const result = await client.updateRecurringBlackoutRule(id, rulePayload);
1564
- if (result.ok) {
1565
- return getSuccessToolResult(
1566
- `recurringBlackoutRule: ${encodePayload(result.value)}`
1567
- );
1568
- }
1569
- return getErrorToolResult(
1570
- `blackout: ${result.error.category} (${result.error.reason})`
1571
- );
1572
- }
1573
- if (name === "lighthouse_blackout_delete") {
1574
- const id = getNumericId(argumentsPayload);
1575
- if (id === null) {
1576
- return getErrorToolResult("blackout: invalid id (rule id required)");
1577
- }
1578
- const result = await client.deleteRecurringBlackoutRule(id);
1579
- if (result.ok) {
1580
- return getSuccessToolResult(`recurringBlackoutRule deleted: ${id}`);
1581
- }
1582
- return getErrorToolResult(
1583
- `blackout: ${result.error.category} (${result.error.reason})`
1584
- );
1585
- }
1586
- if (name === "lighthouse_forecast_manual") {
1587
- const id = getNumericId(argumentsPayload);
1588
- if (id === null) {
1589
- return getErrorToolResult("forecast: invalid id (team id required)");
1590
- }
1591
- const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1592
- const remainingItems = typeof payload.remainingItems === "number" ? payload.remainingItems : void 0;
1593
- const targetDate = typeof payload.targetDate === "string" ? payload.targetDate : void 0;
1594
- const applyFilterOverride = getApplyFilterOverride(argumentsPayload);
1595
- const result = await client.runManualForecast(id, {
1596
- remainingItems,
1597
- targetDate,
1598
- applyFilterOverride
1599
- });
1600
- if (result.ok) {
1601
- return getSuccessToolResult(`forecast: ${encodePayload(result.value)}`);
1602
- }
1603
- return getErrorToolResult(
1604
- `forecast: ${result.error.category} (${result.error.reason})`
1605
- );
1606
- }
1607
- if (name === "lighthouse_forecast_backtest") {
1608
- const id = getNumericId(argumentsPayload);
1609
- if (id === null) {
1610
- return getErrorToolResult("backtest: invalid id (team id required)");
1611
- }
1612
- const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1613
- const startDate = typeof payload.startDate === "string" ? payload.startDate : void 0;
1614
- const endDate = typeof payload.endDate === "string" ? payload.endDate : void 0;
1615
- const historicalStartDate = typeof payload.historicalStartDate === "string" ? payload.historicalStartDate : void 0;
1616
- const historicalEndDate = typeof payload.historicalEndDate === "string" ? payload.historicalEndDate : void 0;
1617
- if (!startDate || !endDate || !historicalStartDate || !historicalEndDate) {
1618
- return getErrorToolResult(
1619
- "backtest: startDate, endDate, historicalStartDate, and historicalEndDate are required"
1620
- );
1621
- }
1622
- const applyFilterOverride = getApplyFilterOverride(argumentsPayload);
1623
- const result = await client.runBacktest(id, {
1624
- startDate,
1625
- endDate,
1626
- historicalStartDate,
1627
- historicalEndDate,
1628
- applyFilterOverride
1629
- });
1630
- if (result.ok) {
1631
- return getSuccessToolResult(`backtest: ${encodePayload(result.value)}`);
1632
- }
1633
- return getErrorToolResult(
1634
- `backtest: ${result.error.category} (${result.error.reason})`
1635
- );
1636
- }
1637
- return getErrorToolResult(`Unknown tool: ${name}`);
1638
- }
1091
+ const readPortfolioWording = (client, portfolioId) => (0, _letpeoplework_lighthouse_client.readAnswerWording)(client, {
1092
+ term: "portfolio",
1093
+ id: portfolioId,
1094
+ read: () => client.getPortfolio(portfolioId)
1639
1095
  });
1640
- var registerMcpTools = (server, dependencies) => {
1641
- const runtime = createMcpCoreRuntime(dependencies);
1642
- for (const tool of toolDefinitions) {
1643
- server.registerTool(
1644
- tool.name,
1645
- {
1646
- description: tool.description,
1647
- inputSchema: toolInputSchemas[tool.name],
1648
- annotations: {
1649
- readOnlyHint: isReadOnlyTool(tool.name),
1650
- idempotentHint: isReadOnlyTool(tool.name),
1651
- openWorldHint: false
1652
- }
1653
- },
1654
- async (argumentsPayload) => {
1655
- const result = await runtime.callTool(tool.name, argumentsPayload);
1656
- return {
1657
- ...result,
1658
- content: [...result.content]
1659
- };
1660
- }
1661
- );
1662
- }
1663
- };
1664
- // Annotate the CommonJS export names for ESM import in node:
1665
- 0 && (module.exports = {
1666
- createMcpCoreRuntime,
1667
- getMcpCorePackageContract,
1668
- registerMcpTools
1096
+ const readMetricsWording = (client, scope, id) => scope === "team" ? readTeamWording(client, id) : readPortfolioWording(client, id);
1097
+ const readForSummary = async (read) => {
1098
+ try {
1099
+ return await read();
1100
+ } catch {
1101
+ return;
1102
+ }
1103
+ };
1104
+ const answeredValue = (read) => read?.ok === true ? read.value : void 0;
1105
+ const summaryOrNull = (summarise) => {
1106
+ try {
1107
+ return summarise();
1108
+ } catch {
1109
+ return null;
1110
+ }
1111
+ };
1112
+ /**
1113
+ * A write's facts block exactly as before, with the line lh confirms it with in a second block. The
1114
+ * confirmation never sits inside the facts: a blackout rule's own `summary` field is Lighthouse's
1115
+ * wording of its schedule and must reach the assistant untouched.
1116
+ */
1117
+ const withConfirmation = (facts, confirm) => withSummaryBlock(facts, summaryOrNull(confirm));
1118
+ /** How many a list holds, in words; left out when the list cannot be read. */
1119
+ const countSummary = (facts, readList, describeCount) => summaryOrNull(() => {
1120
+ const listed = readList(facts);
1121
+ return listed === null ? null : describeCount(listed.length);
1669
1122
  });
1123
+ /** A refresh as before, confirmed as queued in the instance's word for a Team or Portfolio. */
1124
+ const answerRefresh = async (client, kind, id, refresh) => {
1125
+ const [result, terms] = await Promise.all([refresh, (0, _letpeoplework_lighthouse_client.readTerms)(client)]);
1126
+ if (!result.ok) return getErrorToolResult(`${kind} refresh: ${result.error.category} (${result.error.reason})`);
1127
+ return withConfirmation(`${kind} refreshed: ${id}`, () => (0, _letpeoplework_lighthouse_client.describeRefreshConfirmation)(kind, id, terms));
1128
+ };
1129
+ /** A written blackout rule as before, confirmed with Lighthouse's own wording of its schedule. */
1130
+ const answerBlackoutRuleWrite = async (verb, write) => {
1131
+ const result = await write;
1132
+ if (!result.ok) return getErrorToolResult(`blackout: ${result.error.category} (${result.error.reason})`);
1133
+ return withConfirmation(`recurringBlackoutRule: ${encodePayload(result.value)}`, () => {
1134
+ const rule = (0, _letpeoplework_lighthouse_client.readWrittenBlackoutRule)(result.value);
1135
+ return rule === null ? null : (0, _letpeoplework_lighthouse_client.describeBlackoutRuleWriteConfirmation)(verb, rule);
1136
+ });
1137
+ };
1138
+ /** The Features under their label with their count in the instance's words, or Lighthouse's refusal as before. */
1139
+ const answerFeatures = async (client, read) => {
1140
+ const [result, terms] = await Promise.all([read, (0, _letpeoplework_lighthouse_client.readTerms)(client)]);
1141
+ if (!result.ok) return getErrorToolResult(`features: ${result.error.category} (${result.error.reason})`);
1142
+ return withSummary("features", result.value, countSummary(result.value, _letpeoplework_lighthouse_client.readFeatureList, (count) => (0, _letpeoplework_lighthouse_client.describeFeatureListCount)(count, terms)));
1143
+ };
1144
+ /**
1145
+ * One per-metric tool's answer: the facts under their label with the metric stated as lh states it, or
1146
+ * Lighthouse's refusal exactly as before. The summary's own reads run beside the metric's.
1147
+ */
1148
+ const answerMetric = async (labels, read, context, summarise) => {
1149
+ const [result, known] = await Promise.all([read, context]);
1150
+ if (!result.ok) return getErrorToolResult(`${labels.refusal}: ${result.error.category} (${result.error.reason})`);
1151
+ return withSummary(labels.answer, result.value, summaryOrNull(() => summarise(result.value, known)));
1152
+ };
1153
+ /** A Team or Portfolio list's facts, then how many it holds, unless a row cannot say what it is. */
1154
+ const answerOwnerList = async (kind, label, read, client) => {
1155
+ const [result, terms] = await Promise.all([read, (0, _letpeoplework_lighthouse_client.readTerms)(client)]);
1156
+ if (!result.ok) return getErrorToolResult(`${label}: ${result.error.category} (${result.error.reason})`);
1157
+ return withSummary(label, result.value, countSummary(result.value, _letpeoplework_lighthouse_client.readOwnerList, (count) => (0, _letpeoplework_lighthouse_client.describeOwnerCount)(kind, count, terms)));
1158
+ };
1159
+ /** One Team or Portfolio: its facts, with the page's heading and settings as its summary when it can be read. */
1160
+ const answerOwner = async (label, read, client, describe) => {
1161
+ const [result, terms] = await Promise.all([read, (0, _letpeoplework_lighthouse_client.readTerms)(client)]);
1162
+ if (!result.ok) return getErrorToolResult(`${label}: ${result.error.category} (${result.error.reason})`);
1163
+ return withSummary(label, result.value, summaryOrNull(() => {
1164
+ const owner = describe.read(result.value);
1165
+ return owner === null ? null : linesOf(...describe.lines(owner, terms));
1166
+ }));
1167
+ };
1168
+ const metricLabels = (scope, metric) => ({
1169
+ answer: `${scope} ${metric}`,
1170
+ refusal: `${scope} metrics`
1171
+ });
1172
+ const overTheRange = (range, wording, view) => view === null ? null : (0, _letpeoplework_lighthouse_client.describeMetricSummary)((0, _letpeoplework_lighthouse_client.describeMetricsHeading)(range, wording), view);
1173
+ const summariseThroughput = (scope, range) => (facts, wording) => {
1174
+ const chart = (0, _letpeoplework_lighthouse_client.readRunChart)(facts, range);
1175
+ return overTheRange(range, wording, chart === null ? null : (0, _letpeoplework_lighthouse_client.describeThroughputDays)(chart, scope, wording.terms));
1176
+ };
1177
+ const summariseCycleTimePercentiles = (range) => (facts, known) => {
1178
+ const percentiles = (0, _letpeoplework_lighthouse_client.readCycleTimePercentiles)({ values: facts });
1179
+ return overTheRange(range, known.wording, percentiles === null ? null : (0, _letpeoplework_lighthouse_client.describeCycleTimeDays)(percentiles, [], known.wording.terms, known.definitionName));
1180
+ };
1181
+ const summariseWorkItemAgePercentiles = (range) => (facts, wording) => {
1182
+ const percentiles = (0, _letpeoplework_lighthouse_client.readWorkItemAgePercentiles)({ values: facts });
1183
+ return percentiles === null ? null : linesOf((0, _letpeoplework_lighthouse_client.describeAsOfHeading)(range, wording), (0, _letpeoplework_lighthouse_client.describeWorkItemAgePercentiles)(percentiles, wording.terms));
1184
+ };
1185
+ const summariseWorkItemAge = (scope) => (facts, known) => {
1186
+ const overTime = (0, _letpeoplework_lighthouse_client.readWorkItemAge)(facts);
1187
+ const percentiles = (0, _letpeoplework_lighthouse_client.readWorkItemAgePercentiles)({ values: known.percentiles });
1188
+ return overTime === null || percentiles === null ? null : (0, _letpeoplework_lighthouse_client.describeMetricSummary)((0, _letpeoplework_lighthouse_client.describeAsOfHeading)(overTime, known.wording), (0, _letpeoplework_lighthouse_client.describeWorkItemAgeDays)(percentiles, overTime, scope, known.wording.terms));
1189
+ };
1190
+ const summariseTotalWorkItemAge = (scope) => (facts, wording) => {
1191
+ const view = (0, _letpeoplework_lighthouse_client.readTotalWorkItemAge)(facts);
1192
+ return view === null ? null : overTheRange(view, wording, (0, _letpeoplework_lighthouse_client.describeTotalWorkItemAgeDays)(view, scope, wording.terms));
1193
+ };
1194
+ const sentenceOf = (line) => line.detail === "" ? `${line.label}: ${line.value}` : `${line.label}: ${line.value} (${line.detail})`;
1195
+ const summariseCurrentWip = (today) => (facts, known) => {
1196
+ const now = (0, _letpeoplework_lighthouse_client.readInProgressItems)(facts, today);
1197
+ if (now === null) return null;
1198
+ const { terms } = known.wording;
1199
+ const blocked = (0, _letpeoplework_lighthouse_client.describeBlockedNow)(now, "team", terms);
1200
+ return linesOf((0, _letpeoplework_lighthouse_client.describeAsOfHeading)({ endDate: today }, known.wording), sentenceOf((0, _letpeoplework_lighthouse_client.describeInProgressNow)(now, "team", terms, known.systemWipLimit ?? void 0)), blocked === null ? null : sentenceOf(blocked), ...(0, _letpeoplework_lighthouse_client.describeWhatWipLeavesUnsaid)(now, "team", terms, known.systemWipLimit));
1201
+ };
1202
+ const readTeamForSummary = async (client, teamId) => {
1203
+ const team = client.getTeam(teamId);
1204
+ const [wording, teamRead] = await Promise.all([(0, _letpeoplework_lighthouse_client.readAnswerWording)(client, {
1205
+ term: "team",
1206
+ id: teamId,
1207
+ read: () => team
1208
+ }), readForSummary(() => team)]);
1209
+ return {
1210
+ wording,
1211
+ team: teamRead?.ok === true ? teamRead : null
1212
+ };
1213
+ };
1214
+ const teamSettingOf = (team, read) => team === null ? null : read(team.value);
1215
+ const readCurrentWipContext = async (client, teamId) => {
1216
+ const { wording, team } = await readTeamForSummary(client, teamId);
1217
+ return {
1218
+ wording,
1219
+ systemWipLimit: teamSettingOf(team, _letpeoplework_lighthouse_client.readSystemWipLimit)
1220
+ };
1221
+ };
1222
+ const sleRiskLines = (view) => [
1223
+ view.title,
1224
+ ...view.sentence === null ? [] : [view.sentence],
1225
+ ...view.rows.map((row) => row.filter((cell) => cell !== "").join(" · "))
1226
+ ].join("\n");
1227
+ const summariseSleRisk = (today) => (facts, known) => {
1228
+ const entries = (0, _letpeoplework_lighthouse_client.readSleRisk)(facts);
1229
+ return entries === null ? null : linesOf((0, _letpeoplework_lighthouse_client.describeAsOfHeading)({ endDate: today }, known.wording), sleRiskLines((0, _letpeoplework_lighthouse_client.describeSleRiskNow)(entries, known.wording, known.serviceLevelExpectation, known.inProgress)));
1230
+ };
1231
+ const readSleRiskContext = async (client, teamId, today) => {
1232
+ const [{ wording, team }, wipRead] = await Promise.all([readTeamForSummary(client, teamId), readForSummary(() => client.getTeamWip(teamId, today))]);
1233
+ return {
1234
+ wording,
1235
+ serviceLevelExpectation: teamSettingOf(team, _letpeoplework_lighthouse_client.readServiceLevelExpectation),
1236
+ inProgress: (0, _letpeoplework_lighthouse_client.readInProgressItems)(answeredValue(wipRead), today)?.items
1237
+ };
1238
+ };
1239
+ const summariseProcessBehaviorChart = (range, chartType) => (facts, wording) => {
1240
+ const chart = (0, _letpeoplework_lighthouse_client.readProcessBehaviorChart)(facts);
1241
+ if (chart === null) return null;
1242
+ const said = (0, _letpeoplework_lighthouse_client.describeProcessBehaviorChart)(chart, chartType, wording.terms);
1243
+ return linesOf((0, _letpeoplework_lighthouse_client.describeMetricsHeading)(range, wording), said.title, said.sentence);
1244
+ };
1245
+ const summariseTimeInState = (scope, range) => (facts, wording) => {
1246
+ const bar = (0, _letpeoplework_lighthouse_client.readTimeInStateBar)(facts);
1247
+ return overTheRange(range, wording, bar === null ? null : (0, _letpeoplework_lighthouse_client.describeTimeInStateDays)(bar, void 0, void 0, scope, wording.terms));
1248
+ };
1249
+ const summariseTimeInStateContributors = (scope, range) => (facts, wording) => {
1250
+ const contributors = (0, _letpeoplework_lighthouse_client.readTimeInStateContributors)(facts);
1251
+ return overTheRange(range, wording, contributors === null ? null : (0, _letpeoplework_lighthouse_client.describeTimeInStateContributorDays)(contributors, scope, wording.terms));
1252
+ };
1253
+ const summariseBlocked = (scope, range) => (facts, wording) => {
1254
+ const view = (0, _letpeoplework_lighthouse_client.readBlocked)({
1255
+ ...range,
1256
+ history: facts
1257
+ });
1258
+ return overTheRange(range, wording, view === null ? null : (0, _letpeoplework_lighthouse_client.describeBlockedDays)(view, scope, wording.terms));
1259
+ };
1260
+ const summarisePercentilesOverTime = (range, argumentsPayload) => (facts, wording) => {
1261
+ const view = (0, _letpeoplework_lighthouse_client.readPercentilesOverTime)({
1262
+ ...range,
1263
+ history: facts,
1264
+ horizon: getHorizonArgument(argumentsPayload)
1265
+ });
1266
+ const ofCycleTime = getPercentilesOverTimeMetricType(argumentsPayload) !== "WorkItemAge" && view !== null && view.history.every((day) => day.metricType === "CycleTime");
1267
+ return overTheRange(range, wording, view !== null && ofCycleTime ? (0, _letpeoplework_lighthouse_client.describePercentilesOverTimeDays)(view, wording.terms) : null);
1268
+ };
1269
+ const summariseProcessBehaviorOverTime = (range, argumentsPayload) => (facts, wording) => {
1270
+ const family = getProcessBehaviorMetricType(argumentsPayload);
1271
+ const view = (0, _letpeoplework_lighthouse_client.readProcessBehaviorOverTime)({
1272
+ ...range,
1273
+ history: facts
1274
+ });
1275
+ return overTheRange(range, wording, view !== null && (family === void 0 || family === "Throughput") ? (0, _letpeoplework_lighthouse_client.describeProcessBehaviorOverTimeDays)(view, wording.terms) : null);
1276
+ };
1277
+ const describeManualForecastAnswer = (facts, wording) => {
1278
+ const forecast = (0, _letpeoplework_lighthouse_client.readManualForecast)(facts);
1279
+ return forecast === null ? null : linesOf((0, _letpeoplework_lighthouse_client.describeManualForecastSummary)(forecast, wording), (0, _letpeoplework_lighthouse_client.describeManualForecastLikelihood)(forecast, wording));
1280
+ };
1281
+ const describeBacktestAnswer = (facts, wording) => {
1282
+ const backtest = (0, _letpeoplework_lighthouse_client.readBacktest)(facts);
1283
+ return backtest === null ? null : linesOf((0, _letpeoplework_lighthouse_client.describeBacktestSummary)(wording), (0, _letpeoplework_lighthouse_client.describeBacktestPeriod)(backtest), (0, _letpeoplework_lighthouse_client.describeBacktestActual)(backtest, wording));
1284
+ };
1285
+ const isObjectRecord = (value) => {
1286
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1287
+ };
1288
+ const getDateRange = (argumentsPayload) => {
1289
+ if (!isObjectRecord(argumentsPayload)) return;
1290
+ const startDate = argumentsPayload.startDate;
1291
+ const endDate = argumentsPayload.endDate;
1292
+ if (typeof startDate === "string" && typeof endDate === "string") return {
1293
+ startDate,
1294
+ endDate
1295
+ };
1296
+ };
1297
+ const getStringArgument = (argumentsPayload, key) => {
1298
+ if (!isObjectRecord(argumentsPayload)) return;
1299
+ const value = argumentsPayload[key];
1300
+ return typeof value === "string" ? value : void 0;
1301
+ };
1302
+ const getItemIdsArgument = (argumentsPayload) => {
1303
+ if (!isObjectRecord(argumentsPayload)) return;
1304
+ const value = argumentsPayload.itemIds;
1305
+ if (!Array.isArray(value)) return;
1306
+ const ids = value.filter((entry) => typeof entry === "number" && Number.isInteger(entry));
1307
+ return ids.length > 0 ? ids : void 0;
1308
+ };
1309
+ const getThroughputFilterView = (argumentsPayload) => {
1310
+ if (!isObjectRecord(argumentsPayload)) return;
1311
+ const view = argumentsPayload.view;
1312
+ return view === "raw" || view === "filtered" ? view : void 0;
1313
+ };
1314
+ const percentilesOverTimeMetricTypes = ["CycleTime", "WorkItemAge"];
1315
+ const getPercentilesOverTimeMetricType = (argumentsPayload) => {
1316
+ const raw = getStringArgument(argumentsPayload, "metricType");
1317
+ return percentilesOverTimeMetricTypes.find((candidate) => candidate === raw);
1318
+ };
1319
+ const getChartType = (argumentsPayload, chartTypes) => {
1320
+ const raw = getStringArgument(argumentsPayload, "metricType");
1321
+ return chartTypes.find((candidate) => candidate === raw);
1322
+ };
1323
+ const getProcessBehaviorMetricType = (argumentsPayload) => {
1324
+ const raw = getStringArgument(argumentsPayload, "metricType");
1325
+ return processBehaviorMetricTypes.find((candidate) => candidate === raw);
1326
+ };
1327
+ const getHorizonArgument = (argumentsPayload) => {
1328
+ if (!isObjectRecord(argumentsPayload)) return;
1329
+ const horizon = argumentsPayload.horizon;
1330
+ return typeof horizon === "number" && Number.isInteger(horizon) ? horizon : void 0;
1331
+ };
1332
+ const getApplyFilterOverride = (argumentsPayload) => {
1333
+ if (!isObjectRecord(argumentsPayload)) return;
1334
+ const value = argumentsPayload.applyFilterOverride;
1335
+ return typeof value === "boolean" ? value : void 0;
1336
+ };
1337
+ const isoDateStringSchema = zod.z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected ISO date in YYYY-MM-DD format.");
1338
+ const weekdaySchema = zod.z.enum([
1339
+ "Sunday",
1340
+ "Monday",
1341
+ "Tuesday",
1342
+ "Wednesday",
1343
+ "Thursday",
1344
+ "Friday",
1345
+ "Saturday"
1346
+ ]);
1347
+ const recurringBlackoutRuleInputSchema = zod.z.object({
1348
+ weekdays: zod.z.array(weekdaySchema),
1349
+ intervalWeeks: zod.z.number().int(),
1350
+ start: isoDateStringSchema,
1351
+ end: isoDateStringSchema.nullable().optional(),
1352
+ description: zod.z.string()
1353
+ });
1354
+ const toolInputSchemas = {
1355
+ lighthouse_health_check: zod.z.object({}),
1356
+ lighthouse_version_get: zod.z.object({}),
1357
+ lighthouse_worktracking_list: zod.z.object({}),
1358
+ lighthouse_worktracking_get: zod.z.object({ id: zod.z.number().int() }),
1359
+ lighthouse_team_list: zod.z.object({}),
1360
+ lighthouse_team_get: zod.z.object({ id: zod.z.number().int() }),
1361
+ lighthouse_team_refresh: zod.z.object({ id: zod.z.number().int() }),
1362
+ lighthouse_team_refinement_get: zod.z.object({ id: zod.z.number().int() }),
1363
+ ...refinementWriteInputSchemas,
1364
+ lighthouse_portfolio_list: zod.z.object({}),
1365
+ lighthouse_portfolio_get: zod.z.object({ id: zod.z.number().int() }),
1366
+ lighthouse_portfolio_refresh: zod.z.object({ id: zod.z.number().int() }),
1367
+ lighthouse_team_metrics_throughput: zod.z.object({
1368
+ id: zod.z.number().int(),
1369
+ startDate: isoDateStringSchema.optional(),
1370
+ endDate: isoDateStringSchema.optional(),
1371
+ view: zod.z.enum(["raw", "filtered"]).optional()
1372
+ }),
1373
+ lighthouse_team_metrics_cycleTimePercentiles: zod.z.object({
1374
+ id: zod.z.number().int(),
1375
+ startDate: isoDateStringSchema.optional(),
1376
+ endDate: isoDateStringSchema.optional(),
1377
+ definitionId: zod.z.number().int().optional()
1378
+ }),
1379
+ lighthouse_team_metrics_workItemAgePercentiles: zod.z.object({
1380
+ id: zod.z.number().int(),
1381
+ startDate: isoDateStringSchema.optional(),
1382
+ endDate: isoDateStringSchema.optional()
1383
+ }),
1384
+ lighthouse_portfolio_metrics_workItemAgePercentiles: zod.z.object({
1385
+ id: zod.z.number().int(),
1386
+ startDate: isoDateStringSchema.optional(),
1387
+ endDate: isoDateStringSchema.optional()
1388
+ }),
1389
+ lighthouse_team_metrics_blockedCountHistory: zod.z.object({
1390
+ id: zod.z.number().int(),
1391
+ startDate: isoDateStringSchema.optional(),
1392
+ endDate: isoDateStringSchema.optional()
1393
+ }),
1394
+ lighthouse_portfolio_metrics_blockedCountHistory: zod.z.object({
1395
+ id: zod.z.number().int(),
1396
+ startDate: isoDateStringSchema.optional(),
1397
+ endDate: isoDateStringSchema.optional()
1398
+ }),
1399
+ lighthouse_team_metrics_wip: zod.z.object({ id: zod.z.number().int() }),
1400
+ lighthouse_team_metrics_sleRisk: zod.z.object({ id: zod.z.number().int() }),
1401
+ lighthouse_team_metrics_processBehaviorChart: zod.z.object({
1402
+ id: zod.z.number().int(),
1403
+ metricType: zod.z.enum(TEAM_CHART_TYPES),
1404
+ startDate: isoDateStringSchema.optional(),
1405
+ endDate: isoDateStringSchema.optional()
1406
+ }),
1407
+ lighthouse_portfolio_metrics_processBehaviorChart: zod.z.object({
1408
+ id: zod.z.number().int(),
1409
+ metricType: zod.z.enum(processBehaviorMetricTypes),
1410
+ startDate: isoDateStringSchema.optional(),
1411
+ endDate: isoDateStringSchema.optional()
1412
+ }),
1413
+ lighthouse_team_metrics_percentilesOverTime: zod.z.object({
1414
+ id: zod.z.number().int(),
1415
+ startDate: isoDateStringSchema.optional(),
1416
+ endDate: isoDateStringSchema.optional(),
1417
+ metricType: zod.z.enum(["CycleTime", "WorkItemAge"]).optional(),
1418
+ horizon: zod.z.number().int().optional()
1419
+ }),
1420
+ lighthouse_portfolio_metrics_percentilesOverTime: zod.z.object({
1421
+ id: zod.z.number().int(),
1422
+ startDate: isoDateStringSchema.optional(),
1423
+ endDate: isoDateStringSchema.optional(),
1424
+ metricType: zod.z.enum(["CycleTime", "WorkItemAge"]).optional(),
1425
+ horizon: zod.z.number().int().optional()
1426
+ }),
1427
+ lighthouse_team_metrics_processBehaviorOverTime: zod.z.object({
1428
+ id: zod.z.number().int(),
1429
+ startDate: isoDateStringSchema.optional(),
1430
+ endDate: isoDateStringSchema.optional(),
1431
+ metricType: zod.z.enum([
1432
+ "Throughput",
1433
+ "WorkItemAge",
1434
+ "Wip",
1435
+ "CycleTime",
1436
+ "Arrivals",
1437
+ "FeatureSize"
1438
+ ]).optional()
1439
+ }),
1440
+ lighthouse_portfolio_metrics_processBehaviorOverTime: zod.z.object({
1441
+ id: zod.z.number().int(),
1442
+ startDate: isoDateStringSchema.optional(),
1443
+ endDate: isoDateStringSchema.optional(),
1444
+ metricType: zod.z.enum([
1445
+ "Throughput",
1446
+ "WorkItemAge",
1447
+ "Wip",
1448
+ "CycleTime",
1449
+ "Arrivals",
1450
+ "FeatureSize"
1451
+ ]).optional()
1452
+ }),
1453
+ lighthouse_team_metrics_workItemAge: zod.z.object({
1454
+ id: zod.z.number().int(),
1455
+ startDate: isoDateStringSchema.optional(),
1456
+ endDate: isoDateStringSchema.optional()
1457
+ }),
1458
+ lighthouse_team_metrics_totalWorkItemAge: zod.z.object({
1459
+ id: zod.z.number().int(),
1460
+ startDate: isoDateStringSchema.optional(),
1461
+ endDate: isoDateStringSchema.optional()
1462
+ }),
1463
+ lighthouse_portfolio_metrics_throughput: zod.z.object({
1464
+ id: zod.z.number().int(),
1465
+ startDate: isoDateStringSchema.optional(),
1466
+ endDate: isoDateStringSchema.optional()
1467
+ }),
1468
+ lighthouse_portfolio_metrics_workItemAge: zod.z.object({
1469
+ id: zod.z.number().int(),
1470
+ startDate: isoDateStringSchema.optional(),
1471
+ endDate: isoDateStringSchema.optional()
1472
+ }),
1473
+ lighthouse_portfolio_metrics_totalWorkItemAge: zod.z.object({
1474
+ id: zod.z.number().int(),
1475
+ startDate: isoDateStringSchema.optional(),
1476
+ endDate: isoDateStringSchema.optional()
1477
+ }),
1478
+ lighthouse_team_metrics_cumulativeStateTime: zod.z.object({
1479
+ id: zod.z.number().int(),
1480
+ startDate: isoDateStringSchema.optional(),
1481
+ endDate: isoDateStringSchema.optional(),
1482
+ itemIds: zod.z.array(zod.z.number().int()).optional()
1483
+ }),
1484
+ lighthouse_team_metrics_cumulativeStateTimeItems: zod.z.object({
1485
+ id: zod.z.number().int(),
1486
+ state: zod.z.string(),
1487
+ startDate: isoDateStringSchema.optional(),
1488
+ endDate: isoDateStringSchema.optional(),
1489
+ itemIds: zod.z.array(zod.z.number().int()).optional()
1490
+ }),
1491
+ lighthouse_team_metrics_cumulativeStateTimeCandidates: zod.z.object({
1492
+ id: zod.z.number().int(),
1493
+ startDate: isoDateStringSchema.optional(),
1494
+ endDate: isoDateStringSchema.optional()
1495
+ }),
1496
+ lighthouse_portfolio_metrics_cumulativeStateTime: zod.z.object({
1497
+ id: zod.z.number().int(),
1498
+ startDate: isoDateStringSchema.optional(),
1499
+ endDate: isoDateStringSchema.optional(),
1500
+ itemIds: zod.z.array(zod.z.number().int()).optional()
1501
+ }),
1502
+ lighthouse_portfolio_metrics_cumulativeStateTimeItems: zod.z.object({
1503
+ id: zod.z.number().int(),
1504
+ state: zod.z.string(),
1505
+ startDate: isoDateStringSchema.optional(),
1506
+ endDate: isoDateStringSchema.optional(),
1507
+ itemIds: zod.z.array(zod.z.number().int()).optional()
1508
+ }),
1509
+ lighthouse_portfolio_metrics_cumulativeStateTimeCandidates: zod.z.object({
1510
+ id: zod.z.number().int(),
1511
+ startDate: isoDateStringSchema.optional(),
1512
+ endDate: isoDateStringSchema.optional()
1513
+ }),
1514
+ lighthouse_feature_get: zod.z.object({
1515
+ ids: zod.z.array(zod.z.number().int()).optional(),
1516
+ refs: zod.z.array(zod.z.string()).optional()
1517
+ }),
1518
+ lighthouse_feature_workitems: zod.z.object({ id: zod.z.number().int() }),
1519
+ lighthouse_delivery_list: zod.z.object({ id: zod.z.number().int() }),
1520
+ lighthouse_delivery_metrics: zod.z.object({
1521
+ id: zod.z.number().int(),
1522
+ detail: zod.z.literal("epics").optional()
1523
+ }),
1524
+ lighthouse_blackout_list: zod.z.object({}),
1525
+ lighthouse_blackout_create: recurringBlackoutRuleInputSchema,
1526
+ lighthouse_blackout_update: recurringBlackoutRuleInputSchema.extend({ id: zod.z.number().int() }),
1527
+ lighthouse_blackout_delete: zod.z.object({ id: zod.z.number().int() }),
1528
+ lighthouse_forecast_manual: zod.z.object({
1529
+ id: zod.z.number().int(),
1530
+ remainingItems: zod.z.number().int().optional(),
1531
+ targetDate: isoDateStringSchema.optional(),
1532
+ applyFilterOverride: zod.z.boolean().optional()
1533
+ }),
1534
+ lighthouse_forecast_backtest: zod.z.object({
1535
+ id: zod.z.number().int(),
1536
+ startDate: isoDateStringSchema,
1537
+ endDate: isoDateStringSchema,
1538
+ historicalStartDate: isoDateStringSchema,
1539
+ historicalEndDate: isoDateStringSchema,
1540
+ applyFilterOverride: zod.z.boolean().optional()
1541
+ })
1542
+ };
1543
+ const WRITING_TOOLS = /* @__PURE__ */ new Set([
1544
+ "lighthouse_team_refinement_vote",
1545
+ "lighthouse_team_refinement_comment",
1546
+ "lighthouse_team_refinement_voteTakeBack"
1547
+ ]);
1548
+ const isReadOnlyTool = (toolName) => !(WRITING_TOOLS.has(toolName) || toolName.endsWith("_refresh") || toolName.endsWith("_create") || toolName.endsWith("_update") || toolName.endsWith("_delete"));
1549
+ const answerToolCall = (dependencies) => async (name, argumentsPayload) => {
1550
+ const client = dependencies.createClient();
1551
+ if (name === "lighthouse_health_check") {
1552
+ const health = await client.checkConnectivity();
1553
+ if (health.category === "success") return withConfirmation("connectivity: success", () => _letpeoplework_lighthouse_client.LIGHTHOUSE_IS_REACHABLE);
1554
+ const healthReasonSuffix = health.reason === void 0 ? "" : ` (${health.reason})`;
1555
+ return getErrorToolResult(`connectivity: ${health.category}${healthReasonSuffix}`);
1556
+ }
1557
+ if (name === "lighthouse_version_get") {
1558
+ const version = await client.getVersion();
1559
+ if (version.ok) return withConfirmation(`version: ${version.value}`, () => (0, _letpeoplework_lighthouse_client.describeVersion)(version.value));
1560
+ return getErrorToolResult(`version: ${version.error.category} (${version.error.reason})`);
1561
+ }
1562
+ if (name === "lighthouse_worktracking_list") {
1563
+ const [connections, terms] = await Promise.all([client.listWorkTrackingConnections(), (0, _letpeoplework_lighthouse_client.readTerms)(client)]);
1564
+ if (connections.ok) return withSummary("worktracking", connections.value, countSummary(connections.value, _letpeoplework_lighthouse_client.readWorkTrackingConnections, (count) => (0, _letpeoplework_lighthouse_client.describeWorkTrackingSystemCount)(count, terms)));
1565
+ return getErrorToolResult(`worktracking: ${connections.error.category} (${connections.error.reason})`);
1566
+ }
1567
+ if (name === "lighthouse_worktracking_get") {
1568
+ const id = getNumericId(argumentsPayload);
1569
+ if (id === null) return getErrorToolResult("worktracking: invalid id");
1570
+ const connection = await client.getWorkTrackingConnection(id);
1571
+ if (connection.ok) return withSummary("worktracking", connection.value, summaryOrNull(() => (0, _letpeoplework_lighthouse_client.describeConnectionSummary)(connection.value)));
1572
+ return getErrorToolResult(`worktracking: ${connection.error.category} (${connection.error.reason})`);
1573
+ }
1574
+ if (name === "lighthouse_team_list") return answerOwnerList("team", "teams", client.listTeams(), client);
1575
+ if (name === "lighthouse_team_get") {
1576
+ const id = getNumericId(argumentsPayload);
1577
+ if (id === null) return getErrorToolResult("team: invalid id");
1578
+ return answerOwner("team", client.getTeam(id), client, {
1579
+ read: _letpeoplework_lighthouse_client.readTeam,
1580
+ lines: _letpeoplework_lighthouse_client.describeTeamSummary
1581
+ });
1582
+ }
1583
+ if (name === "lighthouse_team_refresh") {
1584
+ const id = getNumericId(argumentsPayload);
1585
+ if (id === null) return getErrorToolResult("team: invalid id");
1586
+ return answerRefresh(client, "team", id, client.refreshTeam(id));
1587
+ }
1588
+ const refinementTool = findRefinementTool(name);
1589
+ if (refinementTool !== void 0) return refinementTool(argumentsPayload, client, dependencies);
1590
+ if (name === "lighthouse_portfolio_list") return answerOwnerList("portfolio", "portfolios", client.listPortfolios(), client);
1591
+ if (name === "lighthouse_portfolio_get") {
1592
+ const id = getNumericId(argumentsPayload);
1593
+ if (id === null) return getErrorToolResult("portfolio: invalid id");
1594
+ return answerOwner("portfolio", client.getPortfolio(id), client, {
1595
+ read: _letpeoplework_lighthouse_client.readPortfolio,
1596
+ lines: _letpeoplework_lighthouse_client.describePortfolioSummary
1597
+ });
1598
+ }
1599
+ if (name === "lighthouse_portfolio_refresh") {
1600
+ const id = getNumericId(argumentsPayload);
1601
+ if (id === null) return getErrorToolResult("portfolio: invalid id");
1602
+ return answerRefresh(client, "portfolio", id, client.refreshPortfolio(id));
1603
+ }
1604
+ if (name === "lighthouse_team_metrics_throughput") {
1605
+ const id = getNumericId(argumentsPayload);
1606
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1607
+ const range = getDateRange(argumentsPayload);
1608
+ const view = getThroughputFilterView(argumentsPayload);
1609
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1610
+ return answerMetric(metricLabels("team", "throughput"), client.getTeamThroughput(id, range, view), readMetricsWording(client, "team", id), summariseThroughput("team", summaryRange));
1611
+ }
1612
+ if (name === "lighthouse_team_metrics_cycleTimePercentiles") {
1613
+ const id = getNumericId(argumentsPayload);
1614
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1615
+ const range = getDateRange(argumentsPayload);
1616
+ const definitionId = getDefinitionId(argumentsPayload);
1617
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1618
+ return answerMetric(metricLabels("team", "cycleTimePercentiles"), client.getTeamCycleTimePercentiles(id, range, definitionId), Promise.all([readTeamWording(client, id), definitionId === void 0 ? void 0 : readForSummary(() => client.getTeamSettings(id)).then((settings) => (0, _letpeoplework_lighthouse_client.readCycleTimeDefinitionName)(answeredValue(settings), definitionId))]).then(([wording, definitionName]) => ({
1619
+ wording,
1620
+ definitionName
1621
+ })), summariseCycleTimePercentiles(summaryRange));
1622
+ }
1623
+ if (name === "lighthouse_team_metrics_workItemAgePercentiles") {
1624
+ const id = getNumericId(argumentsPayload);
1625
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1626
+ const range = getDateRange(argumentsPayload);
1627
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1628
+ return answerMetric(metricLabels("team", "workItemAgePercentiles"), client.getTeamWorkItemAgePercentiles(id, range), readMetricsWording(client, "team", id), summariseWorkItemAgePercentiles(summaryRange));
1629
+ }
1630
+ if (name === "lighthouse_portfolio_metrics_workItemAgePercentiles") {
1631
+ const id = getNumericId(argumentsPayload);
1632
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1633
+ const range = getDateRange(argumentsPayload);
1634
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1635
+ return answerMetric(metricLabels("portfolio", "workItemAgePercentiles"), client.getPortfolioWorkItemAgePercentiles(id, range), readMetricsWording(client, "portfolio", id), summariseWorkItemAgePercentiles(summaryRange));
1636
+ }
1637
+ if (name === "lighthouse_team_metrics_blockedCountHistory") {
1638
+ const id = getNumericId(argumentsPayload);
1639
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1640
+ const range = getDateRange(argumentsPayload);
1641
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1642
+ return answerMetric(metricLabels("team", "blockedCountHistory"), client.getTeamBlockedCountHistory(id, range), readMetricsWording(client, "team", id), summariseBlocked("team", summaryRange));
1643
+ }
1644
+ if (name === "lighthouse_portfolio_metrics_blockedCountHistory") {
1645
+ const id = getNumericId(argumentsPayload);
1646
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1647
+ const range = getDateRange(argumentsPayload);
1648
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1649
+ return answerMetric(metricLabels("portfolio", "blockedCountHistory"), client.getPortfolioBlockedCountHistory(id, range), readMetricsWording(client, "portfolio", id), summariseBlocked("portfolio", summaryRange));
1650
+ }
1651
+ if (name === "lighthouse_team_metrics_wip") {
1652
+ const id = getNumericId(argumentsPayload);
1653
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1654
+ const today = (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)().endDate;
1655
+ return answerMetric(metricLabels("team", "wip"), client.getTeamWip(id, today), readCurrentWipContext(client, id), summariseCurrentWip(today));
1656
+ }
1657
+ if (name === "lighthouse_team_metrics_sleRisk") {
1658
+ const id = getNumericId(argumentsPayload);
1659
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1660
+ const today = (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)().endDate;
1661
+ return answerMetric(metricLabels("team", "sleRisk"), client.getTeamSleRisk(id), readSleRiskContext(client, id, today), summariseSleRisk(today));
1662
+ }
1663
+ if (name === "lighthouse_team_metrics_processBehaviorChart") {
1664
+ const id = getNumericId(argumentsPayload);
1665
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1666
+ const chartType = getChartType(argumentsPayload, TEAM_CHART_TYPES);
1667
+ if (chartType === void 0) return getErrorToolResult("team metrics: invalid metricType");
1668
+ const range = getDateRange(argumentsPayload) ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1669
+ return answerMetric(metricLabels("team", "processBehaviorChart"), client.getTeamProcessBehaviorChart(id, range, chartType), readMetricsWording(client, "team", id), summariseProcessBehaviorChart(range, chartType));
1670
+ }
1671
+ if (name === "lighthouse_portfolio_metrics_processBehaviorChart") {
1672
+ const id = getNumericId(argumentsPayload);
1673
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1674
+ const chartType = getChartType(argumentsPayload, processBehaviorMetricTypes);
1675
+ if (chartType === void 0) return getErrorToolResult("portfolio metrics: invalid metricType");
1676
+ const range = getDateRange(argumentsPayload) ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1677
+ return answerMetric(metricLabels("portfolio", "processBehaviorChart"), client.getPortfolioProcessBehaviorChart(id, range, chartType), readMetricsWording(client, "portfolio", id), summariseProcessBehaviorChart(range, chartType));
1678
+ }
1679
+ if (name === "lighthouse_team_metrics_percentilesOverTime") {
1680
+ const id = getNumericId(argumentsPayload);
1681
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1682
+ const range = getDateRange(argumentsPayload);
1683
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1684
+ return answerMetric(metricLabels("team", "percentilesOverTime"), client.getTeamPercentilesOverTime(id, range, getPercentilesOverTimeMetricType(argumentsPayload), getHorizonArgument(argumentsPayload)), readMetricsWording(client, "team", id), summarisePercentilesOverTime(summaryRange, argumentsPayload));
1685
+ }
1686
+ if (name === "lighthouse_portfolio_metrics_percentilesOverTime") {
1687
+ const id = getNumericId(argumentsPayload);
1688
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1689
+ const range = getDateRange(argumentsPayload);
1690
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1691
+ return answerMetric(metricLabels("portfolio", "percentilesOverTime"), client.getPortfolioPercentilesOverTime(id, range, getPercentilesOverTimeMetricType(argumentsPayload), getHorizonArgument(argumentsPayload)), readMetricsWording(client, "portfolio", id), summarisePercentilesOverTime(summaryRange, argumentsPayload));
1692
+ }
1693
+ if (name === "lighthouse_team_metrics_processBehaviorOverTime") {
1694
+ const id = getNumericId(argumentsPayload);
1695
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1696
+ const range = getDateRange(argumentsPayload);
1697
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1698
+ return answerMetric(metricLabels("team", "processBehaviorOverTime"), client.getTeamProcessBehaviorOverTime(id, range, getProcessBehaviorMetricType(argumentsPayload)), readMetricsWording(client, "team", id), summariseProcessBehaviorOverTime(summaryRange, argumentsPayload));
1699
+ }
1700
+ if (name === "lighthouse_portfolio_metrics_processBehaviorOverTime") {
1701
+ const id = getNumericId(argumentsPayload);
1702
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1703
+ const range = getDateRange(argumentsPayload);
1704
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1705
+ return answerMetric(metricLabels("portfolio", "processBehaviorOverTime"), client.getPortfolioProcessBehaviorOverTime(id, range, getProcessBehaviorMetricType(argumentsPayload)), readMetricsWording(client, "portfolio", id), summariseProcessBehaviorOverTime(summaryRange, argumentsPayload));
1706
+ }
1707
+ if (name === "lighthouse_team_metrics_cumulativeStateTime") {
1708
+ const id = getNumericId(argumentsPayload);
1709
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1710
+ const range = getDateRange(argumentsPayload);
1711
+ return answerMetric(metricLabels("team", "cumulativeStateTime"), client.getTeamCumulativeStateTime(id, range, getItemIdsArgument(argumentsPayload)), readMetricsWording(client, "team", id), summariseTimeInState("team", range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)()));
1712
+ }
1713
+ if (name === "lighthouse_team_metrics_cumulativeStateTimeItems") {
1714
+ const id = getNumericId(argumentsPayload);
1715
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1716
+ const state = getStringArgument(argumentsPayload, "state");
1717
+ if (state === void 0) return getErrorToolResult("team metrics: missing state");
1718
+ const range = getDateRange(argumentsPayload);
1719
+ return answerMetric(metricLabels("team", "cumulativeStateTimeItems"), client.getTeamCumulativeStateTimeItems(id, state, range, getItemIdsArgument(argumentsPayload)), readMetricsWording(client, "team", id), summariseTimeInStateContributors("team", range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)()));
1720
+ }
1721
+ if (name === "lighthouse_team_metrics_cumulativeStateTimeCandidates") {
1722
+ const id = getNumericId(argumentsPayload);
1723
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1724
+ const result = await client.getTeamCumulativeStateTimeCandidates(id, getDateRange(argumentsPayload));
1725
+ if (result.ok) return getSuccessToolResult(`team cumulativeStateTimeCandidates: ${encodePayload(result.value)}`);
1726
+ return getErrorToolResult(`team metrics: ${result.error.category} (${result.error.reason})`);
1727
+ }
1728
+ if (name === "lighthouse_portfolio_metrics_cumulativeStateTime") {
1729
+ const id = getNumericId(argumentsPayload);
1730
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1731
+ const range = getDateRange(argumentsPayload);
1732
+ return answerMetric(metricLabels("portfolio", "cumulativeStateTime"), client.getPortfolioCumulativeStateTime(id, range, getItemIdsArgument(argumentsPayload)), readMetricsWording(client, "portfolio", id), summariseTimeInState("portfolio", range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)()));
1733
+ }
1734
+ if (name === "lighthouse_portfolio_metrics_cumulativeStateTimeItems") {
1735
+ const id = getNumericId(argumentsPayload);
1736
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1737
+ const state = getStringArgument(argumentsPayload, "state");
1738
+ if (state === void 0) return getErrorToolResult("portfolio metrics: missing state");
1739
+ const range = getDateRange(argumentsPayload);
1740
+ return answerMetric(metricLabels("portfolio", "cumulativeStateTimeItems"), client.getPortfolioCumulativeStateTimeItems(id, state, range, getItemIdsArgument(argumentsPayload)), readMetricsWording(client, "portfolio", id), summariseTimeInStateContributors("portfolio", range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)()));
1741
+ }
1742
+ if (name === "lighthouse_portfolio_metrics_cumulativeStateTimeCandidates") {
1743
+ const id = getNumericId(argumentsPayload);
1744
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1745
+ const result = await client.getPortfolioCumulativeStateTimeCandidates(id, getDateRange(argumentsPayload));
1746
+ if (result.ok) return getSuccessToolResult(`portfolio cumulativeStateTimeCandidates: ${encodePayload(result.value)}`);
1747
+ return getErrorToolResult(`portfolio metrics: ${result.error.category} (${result.error.reason})`);
1748
+ }
1749
+ if (name === "lighthouse_portfolio_metrics_throughput") {
1750
+ const id = getNumericId(argumentsPayload);
1751
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1752
+ const range = getDateRange(argumentsPayload);
1753
+ const summaryRange = range ?? (0, _letpeoplework_lighthouse_client.getDefaultMetricsDateRange)();
1754
+ return answerMetric(metricLabels("portfolio", "throughput"), client.getPortfolioThroughput(id, range), readMetricsWording(client, "portfolio", id), summariseThroughput("portfolio", summaryRange));
1755
+ }
1756
+ if (name === "lighthouse_team_metrics_workItemAge") {
1757
+ const id = getNumericId(argumentsPayload);
1758
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1759
+ const range = getDateRange(argumentsPayload);
1760
+ return answerMetric(metricLabels("team", "workItemAge"), client.getTeamWorkItemAgeOverTime(id, range), Promise.all([readMetricsWording(client, "team", id), readForSummary(() => client.getTeamWorkItemAgePercentiles(id, range))]).then(([wording, percentiles]) => ({
1761
+ wording,
1762
+ percentiles: answeredValue(percentiles)
1763
+ })), summariseWorkItemAge("team"));
1764
+ }
1765
+ if (name === "lighthouse_team_metrics_totalWorkItemAge") {
1766
+ const id = getNumericId(argumentsPayload);
1767
+ if (id === null) return getErrorToolResult("team metrics: invalid id");
1768
+ const range = getDateRange(argumentsPayload);
1769
+ return answerMetric(metricLabels("team", "totalWorkItemAge"), client.getTeamTotalWorkItemAgeOverTime(id, range), readMetricsWording(client, "team", id), summariseTotalWorkItemAge("team"));
1770
+ }
1771
+ if (name === "lighthouse_portfolio_metrics_workItemAge") {
1772
+ const id = getNumericId(argumentsPayload);
1773
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1774
+ const range = getDateRange(argumentsPayload);
1775
+ return answerMetric(metricLabels("portfolio", "workItemAge"), client.getPortfolioWorkItemAgeOverTime(id, range), Promise.all([readMetricsWording(client, "portfolio", id), readForSummary(() => client.getPortfolioWorkItemAgePercentiles(id, range))]).then(([wording, percentiles]) => ({
1776
+ wording,
1777
+ percentiles: answeredValue(percentiles)
1778
+ })), summariseWorkItemAge("portfolio"));
1779
+ }
1780
+ if (name === "lighthouse_portfolio_metrics_totalWorkItemAge") {
1781
+ const id = getNumericId(argumentsPayload);
1782
+ if (id === null) return getErrorToolResult("portfolio metrics: invalid id");
1783
+ const range = getDateRange(argumentsPayload);
1784
+ return answerMetric(metricLabels("portfolio", "totalWorkItemAge"), client.getPortfolioTotalWorkItemAgeOverTime(id, range), readMetricsWording(client, "portfolio", id), summariseTotalWorkItemAge("portfolio"));
1785
+ }
1786
+ if (name === "lighthouse_feature_get") {
1787
+ const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1788
+ const idsValue = payload.ids;
1789
+ const refsValue = payload.refs;
1790
+ if (Array.isArray(idsValue) && idsValue.length > 0) {
1791
+ const ids = idsValue.filter((v) => typeof v === "number");
1792
+ return answerFeatures(client, client.getFeaturesByIds(ids));
1793
+ }
1794
+ if (Array.isArray(refsValue) && refsValue.length > 0) {
1795
+ const refs = refsValue.filter((v) => typeof v === "string");
1796
+ return answerFeatures(client, client.getFeaturesByReferences(refs));
1797
+ }
1798
+ return getErrorToolResult("features: provide ids (array of numbers) or refs (array of strings)");
1799
+ }
1800
+ if (name === "lighthouse_feature_workitems") {
1801
+ const id = getNumericId(argumentsPayload);
1802
+ if (id === null) return getErrorToolResult("feature workitems: invalid id");
1803
+ const [result, wording] = await Promise.all([client.getFeatureWorkItems(id), (0, _letpeoplework_lighthouse_client.readFeatureWording)(client, id)]);
1804
+ if (!result.ok) return getErrorToolResult(`feature workitems: ${result.error.category} (${result.error.reason})`);
1805
+ return withSummary("feature workitems", result.value, summaryOrNull(() => {
1806
+ const items = (0, _letpeoplework_lighthouse_client.readFeatureWorkItems)(result.value);
1807
+ return items === null ? null : (0, _letpeoplework_lighthouse_client.describeFeatureWorkItemsHeading)(wording.name, items.length, wording.terms);
1808
+ }));
1809
+ }
1810
+ if (name === "lighthouse_delivery_list") {
1811
+ const id = getNumericId(argumentsPayload);
1812
+ if (id === null) return getErrorToolResult("delivery: invalid id (portfolio id required)");
1813
+ const [result, terms] = await Promise.all([client.listDeliveries(id), (0, _letpeoplework_lighthouse_client.readTerms)(client)]);
1814
+ if (!result.ok) return getErrorToolResult(`delivery: ${result.error.category} (${result.error.reason})`);
1815
+ return withSummary("deliveries", result.value, countSummary(result.value, _letpeoplework_lighthouse_client.readDeliveryList, (count) => (0, _letpeoplework_lighthouse_client.describeDeliveryCount)(count, terms)));
1816
+ }
1817
+ if (name === "lighthouse_delivery_metrics") {
1818
+ const id = getNumericId(argumentsPayload);
1819
+ if (id === null) return getErrorToolResult("delivery metrics: invalid id (delivery id required)");
1820
+ const wantsEpics = argumentsPayload?.detail === "epics";
1821
+ const [result, terms] = await Promise.all([client.getDeliveryMetricsHistory(id), (0, _letpeoplework_lighthouse_client.readTerms)(client)]);
1822
+ if (!result.ok) return getErrorToolResult(`delivery metrics: ${result.error.category} (${result.error.reason})`);
1823
+ const payload = wantsEpics ? result.value : (0, _letpeoplework_lighthouse_client.summariseDeliveryMetricsHistory)(result.value);
1824
+ return withSummary("delivery metrics", payload, summaryOrNull(() => {
1825
+ const history = (0, _letpeoplework_lighthouse_client.readDeliveryMetricsHistory)(result.value);
1826
+ return history === null ? null : (0, _letpeoplework_lighthouse_client.describeDeliveryMetricsHeading)(history, id, terms);
1827
+ }));
1828
+ }
1829
+ if (name === "lighthouse_blackout_list") {
1830
+ const result = await client.getRecurringBlackoutRules();
1831
+ if (result.ok) return withSummary("recurringBlackoutRules", result.value, countSummary(result.value, _letpeoplework_lighthouse_client.readBlackoutRules, _letpeoplework_lighthouse_client.describeBlackoutRuleCount));
1832
+ return getErrorToolResult(`blackout: ${result.error.category} (${result.error.reason})`);
1833
+ }
1834
+ if (name === "lighthouse_blackout_create") {
1835
+ const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1836
+ return answerBlackoutRuleWrite("Created", client.createRecurringBlackoutRule(payload));
1837
+ }
1838
+ if (name === "lighthouse_blackout_update") {
1839
+ const id = getNumericId(argumentsPayload);
1840
+ if (id === null) return getErrorToolResult("blackout: invalid id (rule id required)");
1841
+ const { id: _ignoredId, ...rulePayload } = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1842
+ return answerBlackoutRuleWrite("Updated", client.updateRecurringBlackoutRule(id, rulePayload));
1843
+ }
1844
+ if (name === "lighthouse_blackout_delete") {
1845
+ const id = getNumericId(argumentsPayload);
1846
+ if (id === null) return getErrorToolResult("blackout: invalid id (rule id required)");
1847
+ const result = await client.deleteRecurringBlackoutRule(id);
1848
+ if (!result.ok) return getErrorToolResult(`blackout: ${result.error.category} (${result.error.reason})`);
1849
+ return withConfirmation(`recurringBlackoutRule deleted: ${id}`, () => (0, _letpeoplework_lighthouse_client.describeBlackoutRuleWriteConfirmation)("Deleted", { id }));
1850
+ }
1851
+ if (name === "lighthouse_forecast_manual") {
1852
+ const id = getNumericId(argumentsPayload);
1853
+ if (id === null) return getErrorToolResult("forecast: invalid id (team id required)");
1854
+ const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1855
+ const remainingItems = typeof payload.remainingItems === "number" ? payload.remainingItems : void 0;
1856
+ const targetDate = typeof payload.targetDate === "string" ? payload.targetDate : void 0;
1857
+ const applyFilterOverride = getApplyFilterOverride(argumentsPayload);
1858
+ const [result, wording] = await Promise.all([client.runManualForecast(id, {
1859
+ remainingItems,
1860
+ targetDate,
1861
+ applyFilterOverride
1862
+ }), readTeamWording(client, id)]);
1863
+ if (result.ok) return withSummary("forecast", result.value, summaryOrNull(() => describeManualForecastAnswer(result.value, wording)));
1864
+ return getErrorToolResult(`forecast: ${result.error.category} (${result.error.reason})`);
1865
+ }
1866
+ if (name === "lighthouse_forecast_backtest") {
1867
+ const id = getNumericId(argumentsPayload);
1868
+ if (id === null) return getErrorToolResult("backtest: invalid id (team id required)");
1869
+ const payload = isObjectRecord(argumentsPayload) ? argumentsPayload : {};
1870
+ const startDate = typeof payload.startDate === "string" ? payload.startDate : void 0;
1871
+ const endDate = typeof payload.endDate === "string" ? payload.endDate : void 0;
1872
+ const historicalStartDate = typeof payload.historicalStartDate === "string" ? payload.historicalStartDate : void 0;
1873
+ const historicalEndDate = typeof payload.historicalEndDate === "string" ? payload.historicalEndDate : void 0;
1874
+ if (!startDate || !endDate || !historicalStartDate || !historicalEndDate) return getErrorToolResult("backtest: startDate, endDate, historicalStartDate, and historicalEndDate are required");
1875
+ const applyFilterOverride = getApplyFilterOverride(argumentsPayload);
1876
+ const [result, wording] = await Promise.all([client.runBacktest(id, {
1877
+ startDate,
1878
+ endDate,
1879
+ historicalStartDate,
1880
+ historicalEndDate,
1881
+ applyFilterOverride
1882
+ }), readTeamWording(client, id)]);
1883
+ if (result.ok) return withSummary("backtest", result.value, summaryOrNull(() => describeBacktestAnswer(result.value, wording)));
1884
+ return getErrorToolResult(`backtest: ${result.error.category} (${result.error.reason})`);
1885
+ }
1886
+ return getErrorToolResult(`Unknown tool: ${name}`);
1887
+ };
1888
+ const countedOf = (name, answered) => isToolResult(answered) ? countedToolResult(answered, usageDataOccurrencesOf(name)) : answered;
1889
+ const createMcpCoreRuntime = (dependencies) => {
1890
+ const answer = answerToolCall(dependencies);
1891
+ const callCountedTool = async (name, argumentsPayload) => countedOf(name, await answer(name, argumentsPayload));
1892
+ return {
1893
+ listTools: () => toolDefinitions,
1894
+ callTool: async (name, argumentsPayload) => (await callCountedTool(name, argumentsPayload)).result,
1895
+ callCountedTool
1896
+ };
1897
+ };
1898
+ const canElicit = (server) => server.server?.getClientCapabilities()?.elicitation !== void 0;
1899
+ const registerMcpTools = (server, dependencies) => {
1900
+ const runtime = createMcpCoreRuntime(dependencies);
1901
+ for (const tool of toolDefinitions) server.registerTool(tool.name, {
1902
+ description: tool.description,
1903
+ inputSchema: toolInputSchemas[tool.name],
1904
+ annotations: {
1905
+ readOnlyHint: isReadOnlyTool(tool.name),
1906
+ idempotentHint: isReadOnlyTool(tool.name),
1907
+ openWorldHint: false
1908
+ }
1909
+ }, async (argumentsPayload, extra) => {
1910
+ const { result, occurrences } = await runtime.callCountedTool(tool.name, argumentsPayload);
1911
+ await dependencies.usageData?.({
1912
+ reached: !result.isError,
1913
+ occurrences,
1914
+ ask: canElicit(server) ? askThroughTheAssistant((params, options) => extra.sendRequest({
1915
+ method: "elicitation/create",
1916
+ params
1917
+ }, _modelcontextprotocol_sdk_types_js.ElicitResultSchema, {
1918
+ ...options,
1919
+ signal: extra.signal
1920
+ })) : void 0
1921
+ });
1922
+ return {
1923
+ ...result,
1924
+ content: [...result.content]
1925
+ };
1926
+ });
1927
+ };
1928
+ //#endregion
1929
+ exports.USAGE_DATA_ELICITATION_MESSAGE = USAGE_DATA_ELICITATION_MESSAGE;
1930
+ exports.USAGE_DATA_QUESTION_TIMEOUT_MS = USAGE_DATA_QUESTION_TIMEOUT_MS;
1931
+ exports.askThroughTheAssistant = askThroughTheAssistant;
1932
+ exports.askingOnceInThisProcess = askingOnceInThisProcess;
1933
+ exports.countedToolResult = countedToolResult;
1934
+ exports.createMcpCoreRuntime = createMcpCoreRuntime;
1935
+ exports.getMcpCorePackageContract = getMcpCorePackageContract;
1936
+ exports.registerMcpTools = registerMcpTools;
1937
+ exports.usageDataOccurrencesOf = usageDataOccurrencesOf;
1938
+ exports.usageDataOccurrencesOfARead = usageDataOccurrencesOfARead;
1939
+ exports.usageDataOccurrencesOfAVote = usageDataOccurrencesOfAVote;