@letpeoplework/lighthouse-mcp-core 1.8.1 → 1.10.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
@@ -426,6 +426,119 @@ const REFINEMENT_TOOLS = /* @__PURE__ */ new Map([
426
426
  /** The handler of a refinement tool, or undefined for a tool that is not one. */
427
427
  const findRefinementTool = (name) => REFINEMENT_TOOLS.get(name);
428
428
  //#endregion
429
+ //#region src/reportTools.ts
430
+ const HOW_LIGHTHOUSE_SHOWS_IT = "Read the lighthouse-reports skill for how Lighthouse shows these values, and quote them as they come.";
431
+ const HOW_THE_ANSWER_READS = "The first block is Lighthouse's answer, unchanged; a second text block, `summary`,";
432
+ const LIST_LABEL = "reports";
433
+ const GET_LABEL = "report";
434
+ const NEEDS_A_SERVER = "Needs a Lighthouse newer than v26.10.10.1.";
435
+ const ownerIdProperty = (kind) => ({
436
+ type: "integer",
437
+ description: `The ${kind}'s numeric id.`
438
+ });
439
+ const reportIdProperty = {
440
+ type: "integer",
441
+ description: "The report's id, as its owner's report list names it under reports[].id."
442
+ };
443
+ const listInputSchema = (kind) => ({
444
+ type: "object",
445
+ properties: { id: ownerIdProperty(kind) },
446
+ required: ["id"],
447
+ additionalProperties: false
448
+ });
449
+ const getInputSchema = (kind) => ({
450
+ type: "object",
451
+ properties: {
452
+ id: ownerIdProperty(kind),
453
+ reportId: reportIdProperty
454
+ },
455
+ required: ["id", "reportId"],
456
+ additionalProperties: false
457
+ });
458
+ const listDescription = (kind) => `List the reports a ${kind} holds by ${kind} ID: each report's id, name, type (templateKey) and when it was created. ${HOW_THE_ANSWER_READS} names the ${kind} and counts its reports in the instance's terminology. ${HOW_LIGHTHOUSE_SHOWS_IT} ${NEEDS_A_SERVER}`;
459
+ const getDescription = (kind) => `Get one of a ${kind}'s reports by ${kind} ID and reportId, exactly as Lighthouse answers it: its Then side as Lighthouse froze it, its Now side read afresh up to today, and every value, a missing value with its reason. ${HOW_LIGHTHOUSE_SHOWS_IT} ${HOW_THE_ANSWER_READS} names the report, its ${kind} and its Then and Now windows. ${NEEDS_A_SERVER}`;
460
+ const reportToolDefinitions = [
461
+ {
462
+ name: "lighthouse_team_report_list",
463
+ description: listDescription("team"),
464
+ inputSchema: listInputSchema("team")
465
+ },
466
+ {
467
+ name: "lighthouse_team_report_get",
468
+ description: getDescription("team"),
469
+ inputSchema: getInputSchema("team")
470
+ },
471
+ {
472
+ name: "lighthouse_portfolio_report_list",
473
+ description: listDescription("portfolio"),
474
+ inputSchema: listInputSchema("portfolio")
475
+ },
476
+ {
477
+ name: "lighthouse_portfolio_report_get",
478
+ description: getDescription("portfolio"),
479
+ inputSchema: getInputSchema("portfolio")
480
+ }
481
+ ];
482
+ const listSchema = zod.z.object({ id: zod.z.number().int() });
483
+ const getSchema = zod.z.object({
484
+ id: zod.z.number().int(),
485
+ reportId: zod.z.number().int()
486
+ });
487
+ const reportInputSchemas = {
488
+ lighthouse_team_report_list: listSchema,
489
+ lighthouse_team_report_get: getSchema,
490
+ lighthouse_portfolio_report_list: listSchema,
491
+ lighthouse_portfolio_report_get: getSchema
492
+ };
493
+ const getReportId = (argumentsPayload) => {
494
+ const reportId = typeof argumentsPayload === "object" && argumentsPayload !== null ? argumentsPayload.reportId : void 0;
495
+ return typeof reportId === "number" && Number.isInteger(reportId) ? reportId : null;
496
+ };
497
+ const answerReportRead = async (client, owner, result, answer) => {
498
+ if (!result.ok) return getErrorToolResult(`${answer.label}: ${await (0, _letpeoplework_lighthouse_client.readReportRefusal)(client, result.error, owner.kind, answer.read)}`);
499
+ const ownerName = await (0, _letpeoplework_lighthouse_client.readReportOwner)(client, owner);
500
+ return withSummaryBlock(`${answer.label}: ${encodePayload(result.value)}`, answer.summarise(result.value, ownerName));
501
+ };
502
+ const invalid = (label, what) => getErrorToolResult(`${label}: invalid ${what}`);
503
+ const reportList = (kind) => async (argumentsPayload, client) => {
504
+ const id = getNumericId(argumentsPayload);
505
+ if (id === null) return invalid(LIST_LABEL, "id");
506
+ const owner = {
507
+ kind,
508
+ id
509
+ };
510
+ return answerReportRead(client, owner, await (0, _letpeoplework_lighthouse_client.readOwnersReports)(client, owner), {
511
+ label: LIST_LABEL,
512
+ read: "list",
513
+ summarise: _letpeoplework_lighthouse_client.summariseReportList
514
+ });
515
+ };
516
+ const reportGet = (kind) => async (argumentsPayload, client) => {
517
+ const id = getNumericId(argumentsPayload);
518
+ if (id === null) return invalid(GET_LABEL, "id");
519
+ const reportId = getReportId(argumentsPayload);
520
+ if (reportId === null) return invalid(GET_LABEL, "reportId");
521
+ const owner = {
522
+ kind,
523
+ id
524
+ };
525
+ const result = await (0, _letpeoplework_lighthouse_client.readOwnersReport)(client, owner, reportId);
526
+ const answered = await answerReportRead(client, owner, result, {
527
+ label: GET_LABEL,
528
+ read: "report",
529
+ summarise: _letpeoplework_lighthouse_client.summariseReport
530
+ });
531
+ return countedToolResult(answered, (0, _letpeoplework_lighthouse_client.reportOpenedOccurrencesOf)(result));
532
+ };
533
+ const REPORT_TOOLS = /* @__PURE__ */ new Map([
534
+ ["lighthouse_team_report_list", reportList("team")],
535
+ ["lighthouse_team_report_get", reportGet("team")],
536
+ ["lighthouse_portfolio_report_list", reportList("portfolio")],
537
+ ["lighthouse_portfolio_report_get", reportGet("portfolio")]
538
+ ]);
539
+ /** The handler of one of the report tools, or undefined for any other tool. */
540
+ const findReportTool = (name) => REPORT_TOOLS.get(name);
541
+ //#endregion
429
542
  //#region src/index.ts
430
543
  const getMcpCorePackageContract = () => ({
431
544
  name: "@letpeoplework/lighthouse-mcp-core",
@@ -561,6 +674,7 @@ const recurringBlackoutRuleProperties = {
561
674
  description: "Human-readable description of the rule."
562
675
  }
563
676
  };
677
+ const BEYOND_HORIZON_NOTE = "A forecast percentile with beyondHorizon true lies beyond Lighthouse's simulation horizon of horizonYears years: its expectedDate is only a stand-in, not a forecast, so say \"More than <horizonYears> years\" and never that date. Both fields are absent on an older Lighthouse, whose dates stand as sent.";
564
678
  const toolDefinitions = [
565
679
  {
566
680
  name: "lighthouse_health_check",
@@ -690,7 +804,7 @@ const toolDefinitions = [
690
804
  },
691
805
  {
692
806
  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.",
807
+ 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, and whether Lighthouse marks it stale (isStale) and why (staleReasons), both absent on an older Lighthouse. 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
808
  inputSchema: {
695
809
  type: "object",
696
810
  properties: {
@@ -824,7 +938,7 @@ const toolDefinitions = [
824
938
  },
825
939
  {
826
940
  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.",
941
+ 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. " + BEYOND_HORIZON_NOTE,
828
942
  inputSchema: {
829
943
  type: "object",
830
944
  properties: {
@@ -849,12 +963,12 @@ const toolDefinitions = [
849
963
  },
850
964
  {
851
965
  name: "lighthouse_delivery_list",
852
- description: "List deliveries for a portfolio by portfolio ID. The answer holds the running deliveries under `active` and the archived ones under `archived`; a `summary` field counts the running ones in the instance's terminology.",
966
+ description: "List deliveries for a portfolio by portfolio ID. The answer holds the running deliveries under `active` and the archived ones under `archived`; a `summary` field counts the running ones in the instance's terminology. " + BEYOND_HORIZON_NOTE,
853
967
  inputSchema: idInputSchema
854
968
  },
855
969
  {
856
970
  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.",
971
+ 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. " + BEYOND_HORIZON_NOTE,
858
972
  inputSchema: {
859
973
  type: "object",
860
974
  properties: {
@@ -918,7 +1032,7 @@ const toolDefinitions = [
918
1032
  },
919
1033
  {
920
1034
  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.",
1035
+ 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. " + BEYOND_HORIZON_NOTE,
922
1036
  inputSchema: {
923
1037
  type: "object",
924
1038
  properties: {
@@ -1058,7 +1172,7 @@ const toolDefinitions = [
1058
1172
  },
1059
1173
  {
1060
1174
  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.",
1175
+ 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), whether Lighthouse marks it stale (isStale) and why (staleReasons: each a kind, TimeInState, BlockedDuration or TimeInStateWhileBlocked, with its days, and the state's stateName on the two time-in-state kinds), and its link (url). isStale and staleReasons are absent on an older Lighthouse, which does not say which work items are stale; never work staleness out yourself. Use it for what is blocked, stale 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, how many are blocked and how many are stale; the first block is the facts, unchanged.",
1062
1176
  inputSchema: idInputSchema
1063
1177
  },
1064
1178
  {
@@ -1075,7 +1189,8 @@ const toolDefinitions = [
1075
1189
  name: "lighthouse_portfolio_metrics_processBehaviorChart",
1076
1190
  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
1191
  inputSchema: chartInputSchema(processBehaviorMetricTypes)
1078
- }
1192
+ },
1193
+ ...reportToolDefinitions
1079
1194
  ];
1080
1195
  const getDefinitionId = (argumentsPayload) => {
1081
1196
  if (typeof argumentsPayload !== "object" || argumentsPayload === null || Array.isArray(argumentsPayload)) return;
@@ -1197,7 +1312,8 @@ const summariseCurrentWip = (today) => (facts, known) => {
1197
1312
  if (now === null) return null;
1198
1313
  const { terms } = known.wording;
1199
1314
  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));
1315
+ const stale = (0, _letpeoplework_lighthouse_client.describeStaleNow)(now, "team", terms);
1316
+ 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), stale === null ? null : sentenceOf(stale), ...(0, _letpeoplework_lighthouse_client.describeWhatWipLeavesUnsaid)(now, "team", terms, known.systemWipLimit));
1201
1317
  };
1202
1318
  const readTeamForSummary = async (client, teamId) => {
1203
1319
  const team = client.getTeam(teamId);
@@ -1361,6 +1477,7 @@ const toolInputSchemas = {
1361
1477
  lighthouse_team_refresh: zod.z.object({ id: zod.z.number().int() }),
1362
1478
  lighthouse_team_refinement_get: zod.z.object({ id: zod.z.number().int() }),
1363
1479
  ...refinementWriteInputSchemas,
1480
+ ...reportInputSchemas,
1364
1481
  lighthouse_portfolio_list: zod.z.object({}),
1365
1482
  lighthouse_portfolio_get: zod.z.object({ id: zod.z.number().int() }),
1366
1483
  lighthouse_portfolio_refresh: zod.z.object({ id: zod.z.number().int() }),
@@ -1587,6 +1704,8 @@ const answerToolCall = (dependencies) => async (name, argumentsPayload) => {
1587
1704
  }
1588
1705
  const refinementTool = findRefinementTool(name);
1589
1706
  if (refinementTool !== void 0) return refinementTool(argumentsPayload, client, dependencies);
1707
+ const reportTool = findReportTool(name);
1708
+ if (reportTool !== void 0) return reportTool(argumentsPayload, client);
1590
1709
  if (name === "lighthouse_portfolio_list") return answerOwnerList("portfolio", "portfolios", client.listPortfolios(), client);
1591
1710
  if (name === "lighthouse_portfolio_get") {
1592
1711
  const id = getNumericId(argumentsPayload);
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { DeliveryMetricsHistory, LighthouseApiResult, LighthouseClient, LighthouseVoterKeyStore, PortfolioDeliveries, RefinementMomentFacts, RefinementWordingSource, TeamRefinement, UsageDataOccurrence, UsageDataQuestionAnswer, UsageDataStep, VotedRow } from "@letpeoplework/lighthouse-client";
1
+ import { DeliveryMetricsHistory, LighthouseApiResult, LighthouseClient, LighthouseVoterKeyStore, PortfolioDeliveries, RefinementMomentFacts, RefinementWordingSource, ReportOwnerSource, ReportReads, TeamRefinement, UsageDataOccurrence, UsageDataQuestionAnswer, UsageDataStep, VotedRow } from "@letpeoplework/lighthouse-client";
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import "zod";
4
4
  import { ElicitRequestFormParams, ElicitResult } from "@modelcontextprotocol/sdk/types.js";
@@ -75,6 +75,9 @@ type ToolError = {
75
75
  };
76
76
  type RefinementToolClient = RefinementWordingSource<ToolError> & Pick<LighthouseClient, "getTeamRefinement" | "castRefinementVote" | "addRefinementComment" | "takeBackRefinementVote" | "getRefinementLog" | "getTerminology">;
77
77
  //#endregion
78
+ //#region src/reportTools.d.ts
79
+ type ReportToolClient = ReportReads & ReportOwnerSource;
80
+ //#endregion
78
81
  //#region src/index.d.ts
79
82
  export type McpCorePackageContract = {
80
83
  readonly name: "@letpeoplework/lighthouse-mcp-core";
@@ -83,7 +86,7 @@ export type McpCorePackageContract = {
83
86
  };
84
87
  export declare const getMcpCorePackageContract: () => McpCorePackageContract;
85
88
  export type McpToolDefinition = {
86
- readonly name: "lighthouse_health_check" | "lighthouse_version_get" | "lighthouse_worktracking_list" | "lighthouse_worktracking_get" | "lighthouse_team_list" | "lighthouse_team_get" | "lighthouse_team_refresh" | "lighthouse_team_refinement_get" | "lighthouse_team_refinement_vote" | "lighthouse_team_refinement_comment" | "lighthouse_team_refinement_voteTakeBack" | "lighthouse_portfolio_list" | "lighthouse_portfolio_get" | "lighthouse_portfolio_refresh" | "lighthouse_team_metrics_throughput" | "lighthouse_team_metrics_cycleTimePercentiles" | "lighthouse_team_metrics_workItemAgePercentiles" | "lighthouse_portfolio_metrics_workItemAgePercentiles" | "lighthouse_team_metrics_workItemAge" | "lighthouse_team_metrics_totalWorkItemAge" | "lighthouse_portfolio_metrics_throughput" | "lighthouse_portfolio_metrics_workItemAge" | "lighthouse_portfolio_metrics_totalWorkItemAge" | "lighthouse_team_metrics_blockedCountHistory" | "lighthouse_portfolio_metrics_blockedCountHistory" | "lighthouse_team_metrics_wip" | "lighthouse_team_metrics_sleRisk" | "lighthouse_team_metrics_processBehaviorChart" | "lighthouse_portfolio_metrics_processBehaviorChart" | "lighthouse_team_metrics_percentilesOverTime" | "lighthouse_portfolio_metrics_percentilesOverTime" | "lighthouse_team_metrics_processBehaviorOverTime" | "lighthouse_portfolio_metrics_processBehaviorOverTime" | "lighthouse_team_metrics_cumulativeStateTime" | "lighthouse_team_metrics_cumulativeStateTimeItems" | "lighthouse_team_metrics_cumulativeStateTimeCandidates" | "lighthouse_portfolio_metrics_cumulativeStateTime" | "lighthouse_portfolio_metrics_cumulativeStateTimeItems" | "lighthouse_portfolio_metrics_cumulativeStateTimeCandidates" | "lighthouse_feature_get" | "lighthouse_feature_workitems" | "lighthouse_delivery_list" | "lighthouse_delivery_metrics" | "lighthouse_blackout_list" | "lighthouse_blackout_create" | "lighthouse_blackout_update" | "lighthouse_blackout_delete" | "lighthouse_forecast_manual" | "lighthouse_forecast_backtest";
89
+ readonly name: "lighthouse_health_check" | "lighthouse_version_get" | "lighthouse_worktracking_list" | "lighthouse_worktracking_get" | "lighthouse_team_list" | "lighthouse_team_get" | "lighthouse_team_refresh" | "lighthouse_team_refinement_get" | "lighthouse_team_refinement_vote" | "lighthouse_team_refinement_comment" | "lighthouse_team_refinement_voteTakeBack" | "lighthouse_portfolio_list" | "lighthouse_portfolio_get" | "lighthouse_portfolio_refresh" | "lighthouse_team_metrics_throughput" | "lighthouse_team_metrics_cycleTimePercentiles" | "lighthouse_team_metrics_workItemAgePercentiles" | "lighthouse_portfolio_metrics_workItemAgePercentiles" | "lighthouse_team_metrics_workItemAge" | "lighthouse_team_metrics_totalWorkItemAge" | "lighthouse_portfolio_metrics_throughput" | "lighthouse_portfolio_metrics_workItemAge" | "lighthouse_portfolio_metrics_totalWorkItemAge" | "lighthouse_team_metrics_blockedCountHistory" | "lighthouse_portfolio_metrics_blockedCountHistory" | "lighthouse_team_metrics_wip" | "lighthouse_team_metrics_sleRisk" | "lighthouse_team_metrics_processBehaviorChart" | "lighthouse_portfolio_metrics_processBehaviorChart" | "lighthouse_team_metrics_percentilesOverTime" | "lighthouse_portfolio_metrics_percentilesOverTime" | "lighthouse_team_metrics_processBehaviorOverTime" | "lighthouse_portfolio_metrics_processBehaviorOverTime" | "lighthouse_team_metrics_cumulativeStateTime" | "lighthouse_team_metrics_cumulativeStateTimeItems" | "lighthouse_team_metrics_cumulativeStateTimeCandidates" | "lighthouse_portfolio_metrics_cumulativeStateTime" | "lighthouse_portfolio_metrics_cumulativeStateTimeItems" | "lighthouse_portfolio_metrics_cumulativeStateTimeCandidates" | "lighthouse_feature_get" | "lighthouse_feature_workitems" | "lighthouse_delivery_list" | "lighthouse_delivery_metrics" | "lighthouse_blackout_list" | "lighthouse_blackout_create" | "lighthouse_blackout_update" | "lighthouse_blackout_delete" | "lighthouse_forecast_manual" | "lighthouse_forecast_backtest" | "lighthouse_team_report_list" | "lighthouse_team_report_get" | "lighthouse_portfolio_report_list" | "lighthouse_portfolio_report_get";
87
90
  readonly description: string;
88
91
  readonly inputSchema: {
89
92
  readonly type: "object";
@@ -669,7 +672,7 @@ type McpRuntimeClient = {
669
672
  readonly reason: string;
670
673
  };
671
674
  }>;
672
- } & Omit<RefinementToolClient, "getTeam">;
675
+ } & Omit<RefinementToolClient, "getTeam"> & Omit<ReportToolClient, "getTeam" | "getPortfolio">;
673
676
  export type McpCoreRuntimeDependencies = {
674
677
  readonly createClient: () => McpRuntimeClient;
675
678
  /** Where usage data goes; without it a tool call does exactly what it always did. */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DeliveryMetricsHistory, LighthouseApiResult, LighthouseClient, LighthouseVoterKeyStore, PortfolioDeliveries, RefinementMomentFacts, RefinementWordingSource, TeamRefinement, UsageDataOccurrence, UsageDataQuestionAnswer, UsageDataStep, VotedRow } from "@letpeoplework/lighthouse-client";
1
+ import { DeliveryMetricsHistory, LighthouseApiResult, LighthouseClient, LighthouseVoterKeyStore, PortfolioDeliveries, RefinementMomentFacts, RefinementWordingSource, ReportOwnerSource, ReportReads, TeamRefinement, UsageDataOccurrence, UsageDataQuestionAnswer, UsageDataStep, VotedRow } from "@letpeoplework/lighthouse-client";
2
2
  import { ElicitRequestFormParams, ElicitResult } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { z } from "zod";
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -75,6 +75,9 @@ type ToolError = {
75
75
  };
76
76
  type RefinementToolClient = RefinementWordingSource<ToolError> & Pick<LighthouseClient, "getTeamRefinement" | "castRefinementVote" | "addRefinementComment" | "takeBackRefinementVote" | "getRefinementLog" | "getTerminology">;
77
77
  //#endregion
78
+ //#region src/reportTools.d.ts
79
+ type ReportToolClient = ReportReads & ReportOwnerSource;
80
+ //#endregion
78
81
  //#region src/index.d.ts
79
82
  export type McpCorePackageContract = {
80
83
  readonly name: "@letpeoplework/lighthouse-mcp-core";
@@ -83,7 +86,7 @@ export type McpCorePackageContract = {
83
86
  };
84
87
  export declare const getMcpCorePackageContract: () => McpCorePackageContract;
85
88
  export type McpToolDefinition = {
86
- readonly name: "lighthouse_health_check" | "lighthouse_version_get" | "lighthouse_worktracking_list" | "lighthouse_worktracking_get" | "lighthouse_team_list" | "lighthouse_team_get" | "lighthouse_team_refresh" | "lighthouse_team_refinement_get" | "lighthouse_team_refinement_vote" | "lighthouse_team_refinement_comment" | "lighthouse_team_refinement_voteTakeBack" | "lighthouse_portfolio_list" | "lighthouse_portfolio_get" | "lighthouse_portfolio_refresh" | "lighthouse_team_metrics_throughput" | "lighthouse_team_metrics_cycleTimePercentiles" | "lighthouse_team_metrics_workItemAgePercentiles" | "lighthouse_portfolio_metrics_workItemAgePercentiles" | "lighthouse_team_metrics_workItemAge" | "lighthouse_team_metrics_totalWorkItemAge" | "lighthouse_portfolio_metrics_throughput" | "lighthouse_portfolio_metrics_workItemAge" | "lighthouse_portfolio_metrics_totalWorkItemAge" | "lighthouse_team_metrics_blockedCountHistory" | "lighthouse_portfolio_metrics_blockedCountHistory" | "lighthouse_team_metrics_wip" | "lighthouse_team_metrics_sleRisk" | "lighthouse_team_metrics_processBehaviorChart" | "lighthouse_portfolio_metrics_processBehaviorChart" | "lighthouse_team_metrics_percentilesOverTime" | "lighthouse_portfolio_metrics_percentilesOverTime" | "lighthouse_team_metrics_processBehaviorOverTime" | "lighthouse_portfolio_metrics_processBehaviorOverTime" | "lighthouse_team_metrics_cumulativeStateTime" | "lighthouse_team_metrics_cumulativeStateTimeItems" | "lighthouse_team_metrics_cumulativeStateTimeCandidates" | "lighthouse_portfolio_metrics_cumulativeStateTime" | "lighthouse_portfolio_metrics_cumulativeStateTimeItems" | "lighthouse_portfolio_metrics_cumulativeStateTimeCandidates" | "lighthouse_feature_get" | "lighthouse_feature_workitems" | "lighthouse_delivery_list" | "lighthouse_delivery_metrics" | "lighthouse_blackout_list" | "lighthouse_blackout_create" | "lighthouse_blackout_update" | "lighthouse_blackout_delete" | "lighthouse_forecast_manual" | "lighthouse_forecast_backtest";
89
+ readonly name: "lighthouse_health_check" | "lighthouse_version_get" | "lighthouse_worktracking_list" | "lighthouse_worktracking_get" | "lighthouse_team_list" | "lighthouse_team_get" | "lighthouse_team_refresh" | "lighthouse_team_refinement_get" | "lighthouse_team_refinement_vote" | "lighthouse_team_refinement_comment" | "lighthouse_team_refinement_voteTakeBack" | "lighthouse_portfolio_list" | "lighthouse_portfolio_get" | "lighthouse_portfolio_refresh" | "lighthouse_team_metrics_throughput" | "lighthouse_team_metrics_cycleTimePercentiles" | "lighthouse_team_metrics_workItemAgePercentiles" | "lighthouse_portfolio_metrics_workItemAgePercentiles" | "lighthouse_team_metrics_workItemAge" | "lighthouse_team_metrics_totalWorkItemAge" | "lighthouse_portfolio_metrics_throughput" | "lighthouse_portfolio_metrics_workItemAge" | "lighthouse_portfolio_metrics_totalWorkItemAge" | "lighthouse_team_metrics_blockedCountHistory" | "lighthouse_portfolio_metrics_blockedCountHistory" | "lighthouse_team_metrics_wip" | "lighthouse_team_metrics_sleRisk" | "lighthouse_team_metrics_processBehaviorChart" | "lighthouse_portfolio_metrics_processBehaviorChart" | "lighthouse_team_metrics_percentilesOverTime" | "lighthouse_portfolio_metrics_percentilesOverTime" | "lighthouse_team_metrics_processBehaviorOverTime" | "lighthouse_portfolio_metrics_processBehaviorOverTime" | "lighthouse_team_metrics_cumulativeStateTime" | "lighthouse_team_metrics_cumulativeStateTimeItems" | "lighthouse_team_metrics_cumulativeStateTimeCandidates" | "lighthouse_portfolio_metrics_cumulativeStateTime" | "lighthouse_portfolio_metrics_cumulativeStateTimeItems" | "lighthouse_portfolio_metrics_cumulativeStateTimeCandidates" | "lighthouse_feature_get" | "lighthouse_feature_workitems" | "lighthouse_delivery_list" | "lighthouse_delivery_metrics" | "lighthouse_blackout_list" | "lighthouse_blackout_create" | "lighthouse_blackout_update" | "lighthouse_blackout_delete" | "lighthouse_forecast_manual" | "lighthouse_forecast_backtest" | "lighthouse_team_report_list" | "lighthouse_team_report_get" | "lighthouse_portfolio_report_list" | "lighthouse_portfolio_report_get";
87
90
  readonly description: string;
88
91
  readonly inputSchema: {
89
92
  readonly type: "object";
@@ -669,7 +672,7 @@ type McpRuntimeClient = {
669
672
  readonly reason: string;
670
673
  };
671
674
  }>;
672
- } & Omit<RefinementToolClient, "getTeam">;
675
+ } & Omit<RefinementToolClient, "getTeam"> & Omit<ReportToolClient, "getTeam" | "getPortfolio">;
673
676
  export type McpCoreRuntimeDependencies = {
674
677
  readonly createClient: () => McpRuntimeClient;
675
678
  /** Where usage data goes; without it a tool call does exactly what it always did. */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { LIGHTHOUSE_IS_REACHABLE, REFINEMENT_ANSWERS, describeAsOfHeading, describeBacktestActual, describeBacktestPeriod, describeBacktestSummary, describeBlackoutRuleCount, describeBlackoutRuleWriteConfirmation, describeBlockedDays, describeBlockedNow, describeConnectionSummary, describeCycleTimeDays, describeDeliveryCount, describeDeliveryMetricsHeading, describeFeatureListCount, describeFeatureWorkItemsHeading, describeInProgressNow, describeManualForecastLikelihood, describeManualForecastSummary, describeMetricSummary, describeMetricsHeading, describeMissingCondition, describeNothingToTakeBack, describeOwnerCount, describePercentilesOverTimeDays, describePortfolioSummary, describeProcessBehaviorChart, describeProcessBehaviorOverTimeDays, describeRecordedComment, describeRecordedVote, describeRefinementSummary, describeRefreshConfirmation, describeSleRiskNow, describeTakenBack, describeTeamSummary, describeThroughputDays, describeTimeInStateContributorDays, describeTimeInStateDays, describeTotalWorkItemAgeDays, describeVersion, describeWhatWipLeavesUnsaid, describeWorkItemAgeDays, describeWorkItemAgePercentiles, describeWorkTrackingSystemCount, getDefaultMetricsDateRange, isMissingItsCondition, isRefinementAnswer, keepVoterKey, myVoteOn, readAnswerWording, readBacktest, readBlackoutRules, readBlocked, readCycleTimeDefinitionName, readCycleTimePercentiles, readDeliveryMetricsHistory, readFeatureList, readFeatureWording, readFeatureWorkItems, readInProgressItems, readManualForecast, readNameOfMyVote, readOwnerList, readPercentilesOverTime, readPortfolio, readPortfolioDeliveries, readProcessBehaviorChart, readProcessBehaviorOverTime, readRefinementWording, readRunChart, readServiceLevelExpectation, readSleRisk, readSystemWipLimit, readTeam, readTerms, readTimeInStateBar, readTimeInStateContributors, readTotalWorkItemAge, readVoteRefusal, readWorkItemAge, readWorkItemAgePercentiles, readWorkTrackingConnections, readWrittenBlackoutRule, refinementDayVerdictOf, sizingMomentOf, summariseDeliveryMetricsHistory } from "@letpeoplework/lighthouse-client";
1
+ import { LIGHTHOUSE_IS_REACHABLE, REFINEMENT_ANSWERS, describeAsOfHeading, describeBacktestActual, describeBacktestPeriod, describeBacktestSummary, describeBlackoutRuleCount, describeBlackoutRuleWriteConfirmation, describeBlockedDays, describeBlockedNow, describeConnectionSummary, describeCycleTimeDays, describeDeliveryCount, describeDeliveryMetricsHeading, describeFeatureListCount, describeFeatureWorkItemsHeading, describeInProgressNow, describeManualForecastLikelihood, describeManualForecastSummary, describeMetricSummary, describeMetricsHeading, describeMissingCondition, describeNothingToTakeBack, describeOwnerCount, describePercentilesOverTimeDays, describePortfolioSummary, describeProcessBehaviorChart, describeProcessBehaviorOverTimeDays, describeRecordedComment, describeRecordedVote, describeRefinementSummary, describeRefreshConfirmation, describeSleRiskNow, describeStaleNow, describeTakenBack, describeTeamSummary, describeThroughputDays, describeTimeInStateContributorDays, describeTimeInStateDays, describeTotalWorkItemAgeDays, describeVersion, describeWhatWipLeavesUnsaid, describeWorkItemAgeDays, describeWorkItemAgePercentiles, describeWorkTrackingSystemCount, getDefaultMetricsDateRange, isMissingItsCondition, isRefinementAnswer, keepVoterKey, myVoteOn, readAnswerWording, readBacktest, readBlackoutRules, readBlocked, readCycleTimeDefinitionName, readCycleTimePercentiles, readDeliveryMetricsHistory, readFeatureList, readFeatureWording, readFeatureWorkItems, readInProgressItems, readManualForecast, readNameOfMyVote, readOwnerList, readOwnersReport, readOwnersReports, readPercentilesOverTime, readPortfolio, readPortfolioDeliveries, readProcessBehaviorChart, readProcessBehaviorOverTime, readRefinementWording, readReportOwner, readReportRefusal, readRunChart, readServiceLevelExpectation, readSleRisk, readSystemWipLimit, readTeam, readTerms, readTimeInStateBar, readTimeInStateContributors, readTotalWorkItemAge, readVoteRefusal, readWorkItemAge, readWorkItemAgePercentiles, readWorkTrackingConnections, readWrittenBlackoutRule, refinementDayVerdictOf, reportOpenedOccurrencesOf, sizingMomentOf, summariseDeliveryMetricsHistory, summariseReport, summariseReportList } from "@letpeoplework/lighthouse-client";
2
2
  import { ElicitResultSchema } from "@modelcontextprotocol/sdk/types.js";
3
3
  import { z } from "zod";
4
4
  import { encode } from "@toon-format/toon";
@@ -425,6 +425,119 @@ const REFINEMENT_TOOLS = /* @__PURE__ */ new Map([
425
425
  /** The handler of a refinement tool, or undefined for a tool that is not one. */
426
426
  const findRefinementTool = (name) => REFINEMENT_TOOLS.get(name);
427
427
  //#endregion
428
+ //#region src/reportTools.ts
429
+ const HOW_LIGHTHOUSE_SHOWS_IT = "Read the lighthouse-reports skill for how Lighthouse shows these values, and quote them as they come.";
430
+ const HOW_THE_ANSWER_READS = "The first block is Lighthouse's answer, unchanged; a second text block, `summary`,";
431
+ const LIST_LABEL = "reports";
432
+ const GET_LABEL = "report";
433
+ const NEEDS_A_SERVER = "Needs a Lighthouse newer than v26.10.10.1.";
434
+ const ownerIdProperty = (kind) => ({
435
+ type: "integer",
436
+ description: `The ${kind}'s numeric id.`
437
+ });
438
+ const reportIdProperty = {
439
+ type: "integer",
440
+ description: "The report's id, as its owner's report list names it under reports[].id."
441
+ };
442
+ const listInputSchema = (kind) => ({
443
+ type: "object",
444
+ properties: { id: ownerIdProperty(kind) },
445
+ required: ["id"],
446
+ additionalProperties: false
447
+ });
448
+ const getInputSchema = (kind) => ({
449
+ type: "object",
450
+ properties: {
451
+ id: ownerIdProperty(kind),
452
+ reportId: reportIdProperty
453
+ },
454
+ required: ["id", "reportId"],
455
+ additionalProperties: false
456
+ });
457
+ const listDescription = (kind) => `List the reports a ${kind} holds by ${kind} ID: each report's id, name, type (templateKey) and when it was created. ${HOW_THE_ANSWER_READS} names the ${kind} and counts its reports in the instance's terminology. ${HOW_LIGHTHOUSE_SHOWS_IT} ${NEEDS_A_SERVER}`;
458
+ const getDescription = (kind) => `Get one of a ${kind}'s reports by ${kind} ID and reportId, exactly as Lighthouse answers it: its Then side as Lighthouse froze it, its Now side read afresh up to today, and every value, a missing value with its reason. ${HOW_LIGHTHOUSE_SHOWS_IT} ${HOW_THE_ANSWER_READS} names the report, its ${kind} and its Then and Now windows. ${NEEDS_A_SERVER}`;
459
+ const reportToolDefinitions = [
460
+ {
461
+ name: "lighthouse_team_report_list",
462
+ description: listDescription("team"),
463
+ inputSchema: listInputSchema("team")
464
+ },
465
+ {
466
+ name: "lighthouse_team_report_get",
467
+ description: getDescription("team"),
468
+ inputSchema: getInputSchema("team")
469
+ },
470
+ {
471
+ name: "lighthouse_portfolio_report_list",
472
+ description: listDescription("portfolio"),
473
+ inputSchema: listInputSchema("portfolio")
474
+ },
475
+ {
476
+ name: "lighthouse_portfolio_report_get",
477
+ description: getDescription("portfolio"),
478
+ inputSchema: getInputSchema("portfolio")
479
+ }
480
+ ];
481
+ const listSchema = z.object({ id: z.number().int() });
482
+ const getSchema = z.object({
483
+ id: z.number().int(),
484
+ reportId: z.number().int()
485
+ });
486
+ const reportInputSchemas = {
487
+ lighthouse_team_report_list: listSchema,
488
+ lighthouse_team_report_get: getSchema,
489
+ lighthouse_portfolio_report_list: listSchema,
490
+ lighthouse_portfolio_report_get: getSchema
491
+ };
492
+ const getReportId = (argumentsPayload) => {
493
+ const reportId = typeof argumentsPayload === "object" && argumentsPayload !== null ? argumentsPayload.reportId : void 0;
494
+ return typeof reportId === "number" && Number.isInteger(reportId) ? reportId : null;
495
+ };
496
+ const answerReportRead = async (client, owner, result, answer) => {
497
+ if (!result.ok) return getErrorToolResult(`${answer.label}: ${await readReportRefusal(client, result.error, owner.kind, answer.read)}`);
498
+ const ownerName = await readReportOwner(client, owner);
499
+ return withSummaryBlock(`${answer.label}: ${encodePayload(result.value)}`, answer.summarise(result.value, ownerName));
500
+ };
501
+ const invalid = (label, what) => getErrorToolResult(`${label}: invalid ${what}`);
502
+ const reportList = (kind) => async (argumentsPayload, client) => {
503
+ const id = getNumericId(argumentsPayload);
504
+ if (id === null) return invalid(LIST_LABEL, "id");
505
+ const owner = {
506
+ kind,
507
+ id
508
+ };
509
+ return answerReportRead(client, owner, await readOwnersReports(client, owner), {
510
+ label: LIST_LABEL,
511
+ read: "list",
512
+ summarise: summariseReportList
513
+ });
514
+ };
515
+ const reportGet = (kind) => async (argumentsPayload, client) => {
516
+ const id = getNumericId(argumentsPayload);
517
+ if (id === null) return invalid(GET_LABEL, "id");
518
+ const reportId = getReportId(argumentsPayload);
519
+ if (reportId === null) return invalid(GET_LABEL, "reportId");
520
+ const owner = {
521
+ kind,
522
+ id
523
+ };
524
+ const result = await readOwnersReport(client, owner, reportId);
525
+ const answered = await answerReportRead(client, owner, result, {
526
+ label: GET_LABEL,
527
+ read: "report",
528
+ summarise: summariseReport
529
+ });
530
+ return countedToolResult(answered, reportOpenedOccurrencesOf(result));
531
+ };
532
+ const REPORT_TOOLS = /* @__PURE__ */ new Map([
533
+ ["lighthouse_team_report_list", reportList("team")],
534
+ ["lighthouse_team_report_get", reportGet("team")],
535
+ ["lighthouse_portfolio_report_list", reportList("portfolio")],
536
+ ["lighthouse_portfolio_report_get", reportGet("portfolio")]
537
+ ]);
538
+ /** The handler of one of the report tools, or undefined for any other tool. */
539
+ const findReportTool = (name) => REPORT_TOOLS.get(name);
540
+ //#endregion
428
541
  //#region src/index.ts
429
542
  const getMcpCorePackageContract = () => ({
430
543
  name: "@letpeoplework/lighthouse-mcp-core",
@@ -560,6 +673,7 @@ const recurringBlackoutRuleProperties = {
560
673
  description: "Human-readable description of the rule."
561
674
  }
562
675
  };
676
+ const BEYOND_HORIZON_NOTE = "A forecast percentile with beyondHorizon true lies beyond Lighthouse's simulation horizon of horizonYears years: its expectedDate is only a stand-in, not a forecast, so say \"More than <horizonYears> years\" and never that date. Both fields are absent on an older Lighthouse, whose dates stand as sent.";
563
677
  const toolDefinitions = [
564
678
  {
565
679
  name: "lighthouse_health_check",
@@ -689,7 +803,7 @@ const toolDefinitions = [
689
803
  },
690
804
  {
691
805
  name: "lighthouse_portfolio_metrics_blockedCountHistory",
692
- 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.",
806
+ 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, and whether Lighthouse marks it stale (isStale) and why (staleReasons), both absent on an older Lighthouse. 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.",
693
807
  inputSchema: {
694
808
  type: "object",
695
809
  properties: {
@@ -823,7 +937,7 @@ const toolDefinitions = [
823
937
  },
824
938
  {
825
939
  name: "lighthouse_feature_get",
826
- 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.",
940
+ 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. " + BEYOND_HORIZON_NOTE,
827
941
  inputSchema: {
828
942
  type: "object",
829
943
  properties: {
@@ -848,12 +962,12 @@ const toolDefinitions = [
848
962
  },
849
963
  {
850
964
  name: "lighthouse_delivery_list",
851
- description: "List deliveries for a portfolio by portfolio ID. The answer holds the running deliveries under `active` and the archived ones under `archived`; a `summary` field counts the running ones in the instance's terminology.",
965
+ description: "List deliveries for a portfolio by portfolio ID. The answer holds the running deliveries under `active` and the archived ones under `archived`; a `summary` field counts the running ones in the instance's terminology. " + BEYOND_HORIZON_NOTE,
852
966
  inputSchema: idInputSchema
853
967
  },
854
968
  {
855
969
  name: "lighthouse_delivery_metrics",
856
- 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.",
970
+ 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. " + BEYOND_HORIZON_NOTE,
857
971
  inputSchema: {
858
972
  type: "object",
859
973
  properties: {
@@ -917,7 +1031,7 @@ const toolDefinitions = [
917
1031
  },
918
1032
  {
919
1033
  name: "lighthouse_forecast_manual",
920
- 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.",
1034
+ 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. " + BEYOND_HORIZON_NOTE,
921
1035
  inputSchema: {
922
1036
  type: "object",
923
1037
  properties: {
@@ -1057,7 +1171,7 @@ const toolDefinitions = [
1057
1171
  },
1058
1172
  {
1059
1173
  name: "lighthouse_team_metrics_wip",
1060
- 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.",
1174
+ 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), whether Lighthouse marks it stale (isStale) and why (staleReasons: each a kind, TimeInState, BlockedDuration or TimeInStateWhileBlocked, with its days, and the state's stateName on the two time-in-state kinds), and its link (url). isStale and staleReasons are absent on an older Lighthouse, which does not say which work items are stale; never work staleness out yourself. Use it for what is blocked, stale 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, how many are blocked and how many are stale; the first block is the facts, unchanged.",
1061
1175
  inputSchema: idInputSchema
1062
1176
  },
1063
1177
  {
@@ -1074,7 +1188,8 @@ const toolDefinitions = [
1074
1188
  name: "lighthouse_portfolio_metrics_processBehaviorChart",
1075
1189
  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.",
1076
1190
  inputSchema: chartInputSchema(processBehaviorMetricTypes)
1077
- }
1191
+ },
1192
+ ...reportToolDefinitions
1078
1193
  ];
1079
1194
  const getDefinitionId = (argumentsPayload) => {
1080
1195
  if (typeof argumentsPayload !== "object" || argumentsPayload === null || Array.isArray(argumentsPayload)) return;
@@ -1196,7 +1311,8 @@ const summariseCurrentWip = (today) => (facts, known) => {
1196
1311
  if (now === null) return null;
1197
1312
  const { terms } = known.wording;
1198
1313
  const blocked = describeBlockedNow(now, "team", terms);
1199
- return linesOf(describeAsOfHeading({ endDate: today }, known.wording), sentenceOf(describeInProgressNow(now, "team", terms, known.systemWipLimit ?? void 0)), blocked === null ? null : sentenceOf(blocked), ...describeWhatWipLeavesUnsaid(now, "team", terms, known.systemWipLimit));
1314
+ const stale = describeStaleNow(now, "team", terms);
1315
+ return linesOf(describeAsOfHeading({ endDate: today }, known.wording), sentenceOf(describeInProgressNow(now, "team", terms, known.systemWipLimit ?? void 0)), blocked === null ? null : sentenceOf(blocked), stale === null ? null : sentenceOf(stale), ...describeWhatWipLeavesUnsaid(now, "team", terms, known.systemWipLimit));
1200
1316
  };
1201
1317
  const readTeamForSummary = async (client, teamId) => {
1202
1318
  const team = client.getTeam(teamId);
@@ -1360,6 +1476,7 @@ const toolInputSchemas = {
1360
1476
  lighthouse_team_refresh: z.object({ id: z.number().int() }),
1361
1477
  lighthouse_team_refinement_get: z.object({ id: z.number().int() }),
1362
1478
  ...refinementWriteInputSchemas,
1479
+ ...reportInputSchemas,
1363
1480
  lighthouse_portfolio_list: z.object({}),
1364
1481
  lighthouse_portfolio_get: z.object({ id: z.number().int() }),
1365
1482
  lighthouse_portfolio_refresh: z.object({ id: z.number().int() }),
@@ -1586,6 +1703,8 @@ const answerToolCall = (dependencies) => async (name, argumentsPayload) => {
1586
1703
  }
1587
1704
  const refinementTool = findRefinementTool(name);
1588
1705
  if (refinementTool !== void 0) return refinementTool(argumentsPayload, client, dependencies);
1706
+ const reportTool = findReportTool(name);
1707
+ if (reportTool !== void 0) return reportTool(argumentsPayload, client);
1589
1708
  if (name === "lighthouse_portfolio_list") return answerOwnerList("portfolio", "portfolios", client.listPortfolios(), client);
1590
1709
  if (name === "lighthouse_portfolio_get") {
1591
1710
  const id = getNumericId(argumentsPayload);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letpeoplework/lighthouse-mcp-core",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "description": "Shared MCP semantics for Lighthouse",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "registry": "https://registry.npmjs.org/"
27
27
  },
28
28
  "dependencies": {
29
- "@letpeoplework/lighthouse-client": "1.10.0",
29
+ "@letpeoplework/lighthouse-client": "1.12.0",
30
30
  "@modelcontextprotocol/sdk": "^1.26.0",
31
31
  "@toon-format/toon": "^4.0.0",
32
32
  "zod": "^4.0.0"