@sentientui/mcp 0.15.0 → 0.17.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/AGENTS.md CHANGED
@@ -33,7 +33,7 @@ Server keys start with `sk_` — required for the management API. Public keys (`
33
33
  | `create_project` | Onboard a new user: creates a project + returns its `pk_` key. Needs an account login (OAuth) — refuses `sk_` project keys and demo tokens. Follow with `get_integration_guide` |
34
34
  | `get_project_stats` | Health status + 24h event/session volume |
35
35
  | `list_components` | Components + variant counts + impression totals |
36
- | `get_variant_performance` | CVR 7d vs prior 7d + momentum direction |
36
+ | `get_variant_performance` | CVR current vs prior window + momentum (range/from/to params; default 7d) |
37
37
  | `get_persona_breakdown` | Cluster distribution + avg reliability per cluster |
38
38
  | `get_goal_funnel` | Goal hit counts + per-variant completion rates |
39
39
  | `list_guardrail_events` | Auto-paused variants (last 24h) |
package/README.md CHANGED
@@ -88,11 +88,11 @@ A sandboxed demo token is provisioned automatically — 10 calls/month, read-onl
88
88
  | `create_project` | Create a new project and return its `pk_` public key (account login required — not usable with an `sk_` project key or demo token) |
89
89
  | `get_project_stats` | Events, sessions, agent calls, and health status |
90
90
  | `list_components` | All adaptive components with variant counts |
91
- | `get_variant_performance` | CVR, momentum, and impressions per variant (7d vs prior 7d) |
91
+ | `get_variant_performance` | CVR, momentum, and impressions per variant (windowed: range 7d/30d/90d/all or from/to; default 7d vs prior 7d) |
92
92
  | `get_insights` | AI-generated narrator observations and advisor recommendations |
93
93
  | `refresh_insights` | Trigger fresh AI insight generation |
94
94
  | `get_persona_breakdown` | Visitor cluster distribution with reliability scores |
95
- | `get_goal_funnel` | Goal hit counts and conversion rates per variant |
95
+ | `get_goal_funnel` | Goal hit counts and conversion rates per variant (windowed; default 30d) |
96
96
  | `list_goals` | Defined goals (id, role, event, status) — includes goals with no conversions yet, so agents can wire a dashboard-defined goal into code |
97
97
  | `list_guardrail_events` | Variants auto-paused by the guardrail (last 24h) |
98
98
  | `get_layout_stats` | Per-persona section layout rankings and reward weights |
@@ -199,7 +199,11 @@ var RENDER_JS = {
199
199
  "variant-performance": `
200
200
  window.__render = function(data){
201
201
  var variants = (data && data.variants) || [];
202
- el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, last 7 days vs prior 7';
202
+ var winText = 'current vs prior period';
203
+ if (data && data.window && data.window.start && data.window.end) {
204
+ winText = String(data.window.start).slice(0, 10) + ' to ' + String(data.window.end).slice(0, 10) + ' vs prior period';
205
+ }
206
+ el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, ' + winText;
203
207
  if (!variants.length) { showEmpty('No variant data available yet.'); return; }
204
208
  var max = variants.reduce(function(m,v){ return Math.max(m, v.currentCvr || 0); }, 0) || 1;
205
209
  var sorted = variants.slice().sort(function(a,b){ return (b.currentCvr||0) - (a.currentCvr||0); });
@@ -331,6 +335,44 @@ import { z as z2 } from "zod";
331
335
  // src/tools/common.ts
332
336
  import { z } from "zod";
333
337
  var projectIdSchema = z.string().uuid().describe("The project UUID");
338
+ function rangeInputSchema(defaultRange) {
339
+ return {
340
+ range: z.enum(["7d", "30d", "90d", "all"]).optional().describe(
341
+ `Calendar window in the project's reporting timezone (default ${defaultRange}). 'all' means all retained data \u2014 the plan's retention window bounds every lookback.`
342
+ ),
343
+ from: z.string().optional().describe(
344
+ "Custom window start (YYYY-MM-DD; overrides range). Must be within the plan retention window."
345
+ ),
346
+ to: z.string().optional().describe("Custom window end (YYYY-MM-DD, inclusive; default today).")
347
+ };
348
+ }
349
+ function rangeQuery(args) {
350
+ const p = new URLSearchParams();
351
+ if (args.from) {
352
+ p.set("from", args.from);
353
+ if (args.to) p.set("to", args.to);
354
+ } else if (args.range) {
355
+ p.set("range", args.range);
356
+ }
357
+ const s = p.toString();
358
+ return s ? `?${s}` : "";
359
+ }
360
+ var windowOutputSchema = z.object({
361
+ range: z.string(),
362
+ start: z.string().nullable(),
363
+ end: z.string().nullable(),
364
+ timezone: z.string(),
365
+ bucketUnit: z.string(),
366
+ retentionDays: z.number().describe("The plan's effective data retention in days"),
367
+ earliestAvailable: z.string().describe("First date retention still holds data for (YYYY-MM-DD)")
368
+ }).optional().describe("The window the server actually used for every count in this response");
369
+ function windowLine(window) {
370
+ if (!window) return "";
371
+ if (window.start && window.end) {
372
+ return `Window: ${window.start.slice(0, 10)} to ${window.end.slice(0, 10)} (${window.timezone}, exclusive end)`;
373
+ }
374
+ return `Window: all retained data (${window.retentionDays}-day retention)`;
375
+ }
334
376
  function apiErrorGuidance(err, extra) {
335
377
  if (extra && Object.prototype.hasOwnProperty.call(extra, err.message)) {
336
378
  return extra[err.message];
@@ -345,6 +387,12 @@ function apiErrorGuidance(err, extra) {
345
387
  default:
346
388
  break;
347
389
  }
390
+ if (err.message === "range_out_of_retention") {
391
+ return "The requested window starts before this plan retains data \u2014 older events have been deleted by the retention purge and cannot be recovered. Ask for a more recent window, or upgrade the SentientUI plan for longer history (Growth keeps 180 days, Scale 365).";
392
+ }
393
+ if (err.message === "invalid_range") {
394
+ return "Invalid time window. Use range '7d', '30d', '90d' or 'all', or from/to as YYYY-MM-DD dates.";
395
+ }
348
396
  if (err.status === 402) {
349
397
  return `This feature requires a higher plan (${err.message}). Upgrade your SentientUI plan, then try again.`;
350
398
  }
@@ -516,8 +564,8 @@ function registerComponentTools(server, client) {
516
564
  "list_components",
517
565
  {
518
566
  title: "List components",
519
- description: "List all adaptive components in a project with variant counts and impression totals.",
520
- inputSchema: { projectId: projectIdSchema },
567
+ description: "List all adaptive components in a project with variant counts and impression totals. Counts cover all retained data by default; pass range or from/to for a window.",
568
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("all")),
521
569
  outputSchema: {
522
570
  components: z3.array(
523
571
  z3.object({
@@ -526,7 +574,8 @@ function registerComponentTools(server, client) {
526
574
  impressions: z3.number(),
527
575
  conversions: z3.number()
528
576
  })
529
- ).describe("Adaptive components in the project (empty if none)")
577
+ ).describe("Adaptive components in the project (empty if none)"),
578
+ window: windowOutputSchema
530
579
  },
531
580
  annotations: {
532
581
  readOnlyHint: true,
@@ -534,16 +583,17 @@ function registerComponentTools(server, client) {
534
583
  openWorldHint: false
535
584
  }
536
585
  },
537
- withApiErrorGuidance(async ({ projectId }) => {
586
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
538
587
  const id = encodeURIComponent(projectId);
539
- const { components } = await client.get(`/projects/${id}/components`);
588
+ const { components, window } = await client.get(`/projects/${id}/components${rangeQuery({ range, from, to })}`);
540
589
  const structuredContent = {
541
590
  components: components.map((c) => ({
542
591
  componentId: c.component_id,
543
592
  variantCount: c.variants.length,
544
593
  impressions: c.total_impressions,
545
594
  conversions: c.total_conversions
546
- }))
595
+ })),
596
+ window
547
597
  };
548
598
  if (!components.length) {
549
599
  return {
@@ -561,19 +611,20 @@ function registerComponentTools(server, client) {
561
611
  "get_variant_performance",
562
612
  {
563
613
  title: "Variant performance",
564
- description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
565
- inputSchema: { projectId: projectIdSchema },
614
+ description: "Get CVR and momentum for all variants in a project over the selected window vs the immediately-preceding window of equal length (default: last 7 calendar days vs prior 7).",
615
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("7d")),
566
616
  _meta: uiMeta("variant-performance"),
567
617
  outputSchema: {
568
618
  variants: z3.array(
569
619
  z3.object({
570
620
  variantId: z3.string(),
571
- currentCvr: z3.number().describe("Conversion rate over the last 7 days (0-1)"),
572
- priorCvr: z3.number().describe("Conversion rate over the prior 7 days (0-1)"),
621
+ currentCvr: z3.number().describe("Conversion rate over the selected window (0-1)"),
622
+ priorCvr: z3.number().describe("Conversion rate over the preceding window (0-1)"),
573
623
  deltaPp: z3.number().describe("Change in percentage points"),
574
624
  momentum: z3.string().describe("Momentum direction: gaining, losing, or stable")
575
625
  })
576
- ).describe("Per-variant performance (empty if no data yet)")
626
+ ).describe("Per-variant performance (empty if no data yet)"),
627
+ window: windowOutputSchema
577
628
  },
578
629
  annotations: {
579
630
  readOnlyHint: true,
@@ -581,10 +632,10 @@ function registerComponentTools(server, client) {
581
632
  openWorldHint: false
582
633
  }
583
634
  },
584
- withApiErrorGuidance(async ({ projectId }) => {
635
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
585
636
  var _a, _b, _c;
586
637
  const id = encodeURIComponent(projectId);
587
- const data = await client.get(`/projects/${id}/trends`);
638
+ const data = await client.get(`/projects/${id}/trends${rangeQuery({ range, from, to })}`);
588
639
  const momentumMap = new Map(((_a = data.momentum) != null ? _a : []).map((m) => [m.variantId, m.direction]));
589
640
  const structuredContent = {
590
641
  variants: ((_b = data.cvr) != null ? _b : []).map((v) => {
@@ -596,7 +647,8 @@ function registerComponentTools(server, client) {
596
647
  deltaPp: v.deltaPp,
597
648
  momentum: (_a2 = momentumMap.get(v.variantId)) != null ? _a2 : "stable"
598
649
  };
599
- })
650
+ }),
651
+ window: data.window
600
652
  };
601
653
  if (!((_c = data.cvr) == null ? void 0 : _c.length)) {
602
654
  return {
@@ -605,12 +657,14 @@ function registerComponentTools(server, client) {
605
657
  _meta: uiMeta("variant-performance")
606
658
  };
607
659
  }
608
- const text = data.cvr.map(
660
+ const lines = data.cvr.map(
609
661
  (v) => {
610
662
  var _a2;
611
663
  return `- ${v.variantId}: CVR ${(v.currentCvr * 100).toFixed(2)}% (prior ${(v.priorCvr * 100).toFixed(2)}%, ${v.deltaPp > 0 ? "+" : ""}${v.deltaPp.toFixed(1)} pp, ${(_a2 = momentumMap.get(v.variantId)) != null ? _a2 : "stable"})`;
612
664
  }
613
- ).join("\n");
665
+ );
666
+ const win = windowLine(data.window);
667
+ const text = (win ? [win, ...lines] : lines).join("\n");
614
668
  return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
615
669
  })
616
670
  );
@@ -624,8 +678,11 @@ var EMPTY_REASON_TEXT = {
624
678
  awaiting_profiles: "Still building visitor profiles before visitors can be grouped.",
625
679
  no_variants: "Nothing to compare yet \u2014 no adaptive component is running.",
626
680
  nothing_moved: "Enough data, and nothing moved this period. That is a result.",
627
- job_never_ran: "The analysis job has not run yet.",
628
- job_stale: "These numbers are out of date \u2014 the analysis job has not run recently."
681
+ // The stale/never-ran texts name the remedy: an agent that is not told about
682
+ // refresh_insights has no way to know it can fix this itself.
683
+ job_never_ran: "The analysis job has not run yet. Call refresh_insights to run it now.",
684
+ job_stale: "These numbers are out of date \u2014 the analysis job has not run recently. Call refresh_insights to regenerate them.",
685
+ data_unavailable: "The project data could not be read just now \u2014 nothing is wrong with the setup. Retry shortly."
629
686
  };
630
687
  function registerInsightTools(server, client) {
631
688
  server.registerTool(
@@ -649,7 +706,14 @@ function registerInsightTools(server, client) {
649
706
  observations: z4.array(z4.string()).describe("Headlines of measured findings"),
650
707
  recommendations: z4.array(z4.string()).describe("Headlines of interpreted findings (Growth tier)"),
651
708
  isStale: z4.boolean().describe("True when the analysis has not run recently"),
652
- generatedAt: z4.string().nullable().describe("ISO timestamp, or null")
709
+ generatedAt: z4.string().nullable().describe("ISO timestamp, or null"),
710
+ nextUnlock: z4.object({
711
+ tier: z4.string(),
712
+ needs: z4.string(),
713
+ have: z4.number(),
714
+ need: z4.number(),
715
+ surface: z4.string().nullable()
716
+ }).nullable().describe("The nearest gap that would raise the confidence tier, when there is one")
653
717
  },
654
718
  annotations: {
655
719
  readOnlyHint: true,
@@ -658,7 +722,7 @@ function registerInsightTools(server, client) {
658
722
  }
659
723
  },
660
724
  withApiErrorGuidance(async ({ projectId }) => {
661
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
725
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
662
726
  const id = encodeURIComponent(projectId);
663
727
  const data = await client.get(`/projects/${id}/evidence-report`);
664
728
  const findings = (_a = data.findings) != null ? _a : [];
@@ -670,17 +734,23 @@ function registerInsightTools(server, client) {
670
734
  const emptyReason = (_d = data.emptyReason) != null ? _d : null;
671
735
  const lines = [];
672
736
  if (emptyReason) lines.push((_e = EMPTY_REASON_TEXT[emptyReason]) != null ? _e : emptyReason);
673
- if (isStale) lines.push("\u26A0 The analysis job has not run recently, so these numbers may have moved.");
737
+ if (isStale) lines.push("\u26A0 The analysis job has not run recently, so these numbers may have moved. Call refresh_insights to regenerate them.");
674
738
  if ((_f = data.interpretation) == null ? void 0 : _f.locked) {
675
739
  lines.push(`${data.interpretation.count || "Some"} recommendations are available on the Growth plan.`);
676
740
  } else if (((_g = data.interpretation) == null ? void 0 : _g.emptyReason) && !emptyReason) {
677
741
  lines.push(`Recommendations: ${(_h = EMPTY_REASON_TEXT[data.interpretation.emptyReason]) != null ? _h : data.interpretation.emptyReason}`);
678
742
  }
679
743
  if (data.generatedAt) lines.push(`Generated: ${new Date(data.generatedAt).toUTCString()}`);
744
+ const nextUnlock = (_i = data.nextUnlock) != null ? _i : null;
745
+ if (nextUnlock) {
746
+ const remaining = nextUnlock.need - nextUnlock.have;
747
+ const where = nextUnlock.surface ? `On ${nextUnlock.surface}: ` : "";
748
+ lines.push(`${where}${remaining} more ${nextUnlock.needs} would unlock the next confidence level (${nextUnlock.tier}).`);
749
+ }
680
750
  if (measured.length) {
681
751
  lines.push("", "Findings:");
682
752
  for (const f of measured) {
683
- const cov = f.provenance.coverage !== null && f.provenance.coverage < 0.999 ? `, covering ${Math.round(f.provenance.coverage * 100)}% of visitors` : "";
753
+ const cov = f.provenance.coverage !== null && f.provenance.coverage < 0.999 ? `, covering ${f.provenance.coverage < 5e-3 ? "under 1%" : `${Math.round(f.provenance.coverage * 100)}%`} of visitors` : "";
684
754
  lines.push(`- [${f.tier}] ${f.headline} (${f.provenance.sample} ${f.provenance.denominatorLabel}${cov})`);
685
755
  }
686
756
  }
@@ -692,7 +762,7 @@ function registerInsightTools(server, client) {
692
762
  content: [{ type: "text", text: lines.join("\n").trim() || "No findings yet." }],
693
763
  structuredContent: {
694
764
  status: findings.length ? "ok" : "empty",
695
- reached: (_i = data.reached) != null ? _i : "observed",
765
+ reached: (_j = data.reached) != null ? _j : "observed",
696
766
  findings: findings.map((f) => ({
697
767
  tier: f.tier,
698
768
  kind: f.kind,
@@ -705,7 +775,8 @@ function registerInsightTools(server, client) {
705
775
  observations,
706
776
  recommendations,
707
777
  isStale,
708
- generatedAt: (_j = data.generatedAt) != null ? _j : null
778
+ generatedAt: (_k = data.generatedAt) != null ? _k : null,
779
+ nextUnlock
709
780
  }
710
781
  };
711
782
  })
@@ -779,8 +850,8 @@ function registerGoalTools(server, client) {
779
850
  "get_goal_funnel",
780
851
  {
781
852
  title: "Goal funnel",
782
- description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown. This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report.",
783
- inputSchema: { projectId: projectIdSchema },
853
+ description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown (default window: last 30 calendar days). This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report.",
854
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("30d")),
784
855
  _meta: uiMeta("goal-funnel"),
785
856
  outputSchema: {
786
857
  currency: z6.string().describe("Project display currency (ISO-4217) for the revenue fields"),
@@ -801,7 +872,8 @@ function registerGoalTools(server, client) {
801
872
  })
802
873
  ).describe("Per-variant breakdown")
803
874
  })
804
- ).describe("Configured goals (empty if none)")
875
+ ).describe("Configured goals (empty if none)"),
876
+ window: windowOutputSchema
805
877
  },
806
878
  annotations: {
807
879
  readOnlyHint: true,
@@ -809,11 +881,12 @@ function registerGoalTools(server, client) {
809
881
  openWorldHint: false
810
882
  }
811
883
  },
812
- withApiErrorGuidance(async ({ projectId }) => {
884
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
813
885
  var _a, _b;
814
886
  const id = encodeURIComponent(projectId);
815
- const data = await client.get(`/projects/${id}/goals`);
887
+ const data = await client.get(`/projects/${id}/goals${rangeQuery({ range, from, to })}`);
816
888
  const structuredContent = {
889
+ window: data.window,
817
890
  currency: (_a = data.currency) != null ? _a : "USD",
818
891
  goals: data.goals.map((g) => {
819
892
  var _a2, _b2, _c;
@@ -841,6 +914,7 @@ function registerGoalTools(server, client) {
841
914
  };
842
915
  }
843
916
  const currency = (_b = data.currency) != null ? _b : "USD";
917
+ const win = windowLine(data.window);
844
918
  const lines = data.goals.flatMap((g) => {
845
919
  var _a2;
846
920
  return [
@@ -849,7 +923,8 @@ function registerGoalTools(server, client) {
849
923
  ""
850
924
  ];
851
925
  });
852
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
926
+ const text = (win ? [win, "", ...lines] : lines).join("\n").trim();
927
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("goal-funnel") };
853
928
  })
854
929
  );
855
930
  server.registerTool(
@@ -1693,8 +1768,12 @@ function registerAgentTrafficTools(server, client) {
1693
1768
  "get_agent_traffic",
1694
1769
  {
1695
1770
  title: "Agent traffic",
1696
- description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Agent traffic is tracked separately and never counted in conversion rate.",
1697
- inputSchema: { projectId: projectIdSchema },
1771
+ description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Defaults to the last 30 days; pass from/to for a custom period (agent logs are retained 30 days on Free/Starter, 90 on Growth+). Agent traffic is tracked separately and never counted in conversion rate.",
1772
+ inputSchema: {
1773
+ projectId: projectIdSchema,
1774
+ from: z14.string().optional().describe("Period start (ISO 8601 date or timestamp). Defaults to 30 days ago."),
1775
+ to: z14.string().optional().describe("Period end (ISO 8601 date or timestamp). Defaults to now.")
1776
+ },
1698
1777
  outputSchema: {
1699
1778
  totals: z14.object({ crawler: z14.number(), api: z14.number(), browser: z14.number() }),
1700
1779
  engines: z14.array(
@@ -1712,9 +1791,14 @@ function registerAgentTrafficTools(server, client) {
1712
1791
  },
1713
1792
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
1714
1793
  },
1715
- withApiErrorGuidance(async ({ projectId }) => {
1794
+ withApiErrorGuidance(async ({ projectId, from, to }) => {
1716
1795
  const id = encodeURIComponent(projectId);
1717
- const s = await client.get(`/projects/${id}/agent-activity/summary`);
1796
+ const qs = new URLSearchParams();
1797
+ if (from) qs.set("from", from);
1798
+ if (to) qs.set("to", to);
1799
+ const s = await client.get(
1800
+ `/projects/${id}/agent-activity/summary${qs.size > 0 ? `?${qs.toString()}` : ""}`
1801
+ );
1718
1802
  const structuredContent = { totals: s.totals, engines: s.engines, intents: s.intents, topPaths: s.topPaths };
1719
1803
  const total = s.totals.crawler + s.totals.api + s.totals.browser;
1720
1804
  if (total === 0) {
@@ -1802,7 +1886,7 @@ function registerAgentTrafficTools(server, client) {
1802
1886
  }
1803
1887
 
1804
1888
  // src/server.ts
1805
- var PKG_VERSION = true ? "0.15.0" : "0.0.0-dev";
1889
+ var PKG_VERSION = true ? "0.17.0" : "0.0.0-dev";
1806
1890
  function createMcpServer(client) {
1807
1891
  const server = new McpServer(
1808
1892
  {
package/dist/index.cjs CHANGED
@@ -78,6 +78,44 @@ var import_zod2 = require("zod");
78
78
  // src/tools/common.ts
79
79
  var import_zod = require("zod");
80
80
  var projectIdSchema = import_zod.z.string().uuid().describe("The project UUID");
81
+ function rangeInputSchema(defaultRange) {
82
+ return {
83
+ range: import_zod.z.enum(["7d", "30d", "90d", "all"]).optional().describe(
84
+ `Calendar window in the project's reporting timezone (default ${defaultRange}). 'all' means all retained data \u2014 the plan's retention window bounds every lookback.`
85
+ ),
86
+ from: import_zod.z.string().optional().describe(
87
+ "Custom window start (YYYY-MM-DD; overrides range). Must be within the plan retention window."
88
+ ),
89
+ to: import_zod.z.string().optional().describe("Custom window end (YYYY-MM-DD, inclusive; default today).")
90
+ };
91
+ }
92
+ function rangeQuery(args) {
93
+ const p = new URLSearchParams();
94
+ if (args.from) {
95
+ p.set("from", args.from);
96
+ if (args.to) p.set("to", args.to);
97
+ } else if (args.range) {
98
+ p.set("range", args.range);
99
+ }
100
+ const s = p.toString();
101
+ return s ? `?${s}` : "";
102
+ }
103
+ var windowOutputSchema = import_zod.z.object({
104
+ range: import_zod.z.string(),
105
+ start: import_zod.z.string().nullable(),
106
+ end: import_zod.z.string().nullable(),
107
+ timezone: import_zod.z.string(),
108
+ bucketUnit: import_zod.z.string(),
109
+ retentionDays: import_zod.z.number().describe("The plan's effective data retention in days"),
110
+ earliestAvailable: import_zod.z.string().describe("First date retention still holds data for (YYYY-MM-DD)")
111
+ }).optional().describe("The window the server actually used for every count in this response");
112
+ function windowLine(window) {
113
+ if (!window) return "";
114
+ if (window.start && window.end) {
115
+ return `Window: ${window.start.slice(0, 10)} to ${window.end.slice(0, 10)} (${window.timezone}, exclusive end)`;
116
+ }
117
+ return `Window: all retained data (${window.retentionDays}-day retention)`;
118
+ }
81
119
  function apiErrorGuidance(err, extra) {
82
120
  if (extra && Object.prototype.hasOwnProperty.call(extra, err.message)) {
83
121
  return extra[err.message];
@@ -92,6 +130,12 @@ function apiErrorGuidance(err, extra) {
92
130
  default:
93
131
  break;
94
132
  }
133
+ if (err.message === "range_out_of_retention") {
134
+ return "The requested window starts before this plan retains data \u2014 older events have been deleted by the retention purge and cannot be recovered. Ask for a more recent window, or upgrade the SentientUI plan for longer history (Growth keeps 180 days, Scale 365).";
135
+ }
136
+ if (err.message === "invalid_range") {
137
+ return "Invalid time window. Use range '7d', '30d', '90d' or 'all', or from/to as YYYY-MM-DD dates.";
138
+ }
95
139
  if (err.status === 402) {
96
140
  return `This feature requires a higher plan (${err.message}). Upgrade your SentientUI plan, then try again.`;
97
141
  }
@@ -393,7 +437,11 @@ var RENDER_JS = {
393
437
  "variant-performance": `
394
438
  window.__render = function(data){
395
439
  var variants = (data && data.variants) || [];
396
- el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, last 7 days vs prior 7';
440
+ var winText = 'current vs prior period';
441
+ if (data && data.window && data.window.start && data.window.end) {
442
+ winText = String(data.window.start).slice(0, 10) + ' to ' + String(data.window.end).slice(0, 10) + ' vs prior period';
443
+ }
444
+ el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, ' + winText;
397
445
  if (!variants.length) { showEmpty('No variant data available yet.'); return; }
398
446
  var max = variants.reduce(function(m,v){ return Math.max(m, v.currentCvr || 0); }, 0) || 1;
399
447
  var sorted = variants.slice().sort(function(a,b){ return (b.currentCvr||0) - (a.currentCvr||0); });
@@ -522,8 +570,8 @@ function registerComponentTools(server, client) {
522
570
  "list_components",
523
571
  {
524
572
  title: "List components",
525
- description: "List all adaptive components in a project with variant counts and impression totals.",
526
- inputSchema: { projectId: projectIdSchema },
573
+ description: "List all adaptive components in a project with variant counts and impression totals. Counts cover all retained data by default; pass range or from/to for a window.",
574
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("all")),
527
575
  outputSchema: {
528
576
  components: import_zod3.z.array(
529
577
  import_zod3.z.object({
@@ -532,7 +580,8 @@ function registerComponentTools(server, client) {
532
580
  impressions: import_zod3.z.number(),
533
581
  conversions: import_zod3.z.number()
534
582
  })
535
- ).describe("Adaptive components in the project (empty if none)")
583
+ ).describe("Adaptive components in the project (empty if none)"),
584
+ window: windowOutputSchema
536
585
  },
537
586
  annotations: {
538
587
  readOnlyHint: true,
@@ -540,16 +589,17 @@ function registerComponentTools(server, client) {
540
589
  openWorldHint: false
541
590
  }
542
591
  },
543
- withApiErrorGuidance(async ({ projectId }) => {
592
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
544
593
  const id = encodeURIComponent(projectId);
545
- const { components } = await client.get(`/projects/${id}/components`);
594
+ const { components, window } = await client.get(`/projects/${id}/components${rangeQuery({ range, from, to })}`);
546
595
  const structuredContent = {
547
596
  components: components.map((c) => ({
548
597
  componentId: c.component_id,
549
598
  variantCount: c.variants.length,
550
599
  impressions: c.total_impressions,
551
600
  conversions: c.total_conversions
552
- }))
601
+ })),
602
+ window
553
603
  };
554
604
  if (!components.length) {
555
605
  return {
@@ -567,19 +617,20 @@ function registerComponentTools(server, client) {
567
617
  "get_variant_performance",
568
618
  {
569
619
  title: "Variant performance",
570
- description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
571
- inputSchema: { projectId: projectIdSchema },
620
+ description: "Get CVR and momentum for all variants in a project over the selected window vs the immediately-preceding window of equal length (default: last 7 calendar days vs prior 7).",
621
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("7d")),
572
622
  _meta: uiMeta("variant-performance"),
573
623
  outputSchema: {
574
624
  variants: import_zod3.z.array(
575
625
  import_zod3.z.object({
576
626
  variantId: import_zod3.z.string(),
577
- currentCvr: import_zod3.z.number().describe("Conversion rate over the last 7 days (0-1)"),
578
- priorCvr: import_zod3.z.number().describe("Conversion rate over the prior 7 days (0-1)"),
627
+ currentCvr: import_zod3.z.number().describe("Conversion rate over the selected window (0-1)"),
628
+ priorCvr: import_zod3.z.number().describe("Conversion rate over the preceding window (0-1)"),
579
629
  deltaPp: import_zod3.z.number().describe("Change in percentage points"),
580
630
  momentum: import_zod3.z.string().describe("Momentum direction: gaining, losing, or stable")
581
631
  })
582
- ).describe("Per-variant performance (empty if no data yet)")
632
+ ).describe("Per-variant performance (empty if no data yet)"),
633
+ window: windowOutputSchema
583
634
  },
584
635
  annotations: {
585
636
  readOnlyHint: true,
@@ -587,10 +638,10 @@ function registerComponentTools(server, client) {
587
638
  openWorldHint: false
588
639
  }
589
640
  },
590
- withApiErrorGuidance(async ({ projectId }) => {
641
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
591
642
  var _a2, _b, _c;
592
643
  const id = encodeURIComponent(projectId);
593
- const data = await client.get(`/projects/${id}/trends`);
644
+ const data = await client.get(`/projects/${id}/trends${rangeQuery({ range, from, to })}`);
594
645
  const momentumMap = new Map(((_a2 = data.momentum) != null ? _a2 : []).map((m) => [m.variantId, m.direction]));
595
646
  const structuredContent = {
596
647
  variants: ((_b = data.cvr) != null ? _b : []).map((v) => {
@@ -602,7 +653,8 @@ function registerComponentTools(server, client) {
602
653
  deltaPp: v.deltaPp,
603
654
  momentum: (_a3 = momentumMap.get(v.variantId)) != null ? _a3 : "stable"
604
655
  };
605
- })
656
+ }),
657
+ window: data.window
606
658
  };
607
659
  if (!((_c = data.cvr) == null ? void 0 : _c.length)) {
608
660
  return {
@@ -611,12 +663,14 @@ function registerComponentTools(server, client) {
611
663
  _meta: uiMeta("variant-performance")
612
664
  };
613
665
  }
614
- const text = data.cvr.map(
666
+ const lines = data.cvr.map(
615
667
  (v) => {
616
668
  var _a3;
617
669
  return `- ${v.variantId}: CVR ${(v.currentCvr * 100).toFixed(2)}% (prior ${(v.priorCvr * 100).toFixed(2)}%, ${v.deltaPp > 0 ? "+" : ""}${v.deltaPp.toFixed(1)} pp, ${(_a3 = momentumMap.get(v.variantId)) != null ? _a3 : "stable"})`;
618
670
  }
619
- ).join("\n");
671
+ );
672
+ const win = windowLine(data.window);
673
+ const text = (win ? [win, ...lines] : lines).join("\n");
620
674
  return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
621
675
  })
622
676
  );
@@ -630,8 +684,11 @@ var EMPTY_REASON_TEXT = {
630
684
  awaiting_profiles: "Still building visitor profiles before visitors can be grouped.",
631
685
  no_variants: "Nothing to compare yet \u2014 no adaptive component is running.",
632
686
  nothing_moved: "Enough data, and nothing moved this period. That is a result.",
633
- job_never_ran: "The analysis job has not run yet.",
634
- job_stale: "These numbers are out of date \u2014 the analysis job has not run recently."
687
+ // The stale/never-ran texts name the remedy: an agent that is not told about
688
+ // refresh_insights has no way to know it can fix this itself.
689
+ job_never_ran: "The analysis job has not run yet. Call refresh_insights to run it now.",
690
+ job_stale: "These numbers are out of date \u2014 the analysis job has not run recently. Call refresh_insights to regenerate them.",
691
+ data_unavailable: "The project data could not be read just now \u2014 nothing is wrong with the setup. Retry shortly."
635
692
  };
636
693
  function registerInsightTools(server, client) {
637
694
  server.registerTool(
@@ -655,7 +712,14 @@ function registerInsightTools(server, client) {
655
712
  observations: import_zod4.z.array(import_zod4.z.string()).describe("Headlines of measured findings"),
656
713
  recommendations: import_zod4.z.array(import_zod4.z.string()).describe("Headlines of interpreted findings (Growth tier)"),
657
714
  isStale: import_zod4.z.boolean().describe("True when the analysis has not run recently"),
658
- generatedAt: import_zod4.z.string().nullable().describe("ISO timestamp, or null")
715
+ generatedAt: import_zod4.z.string().nullable().describe("ISO timestamp, or null"),
716
+ nextUnlock: import_zod4.z.object({
717
+ tier: import_zod4.z.string(),
718
+ needs: import_zod4.z.string(),
719
+ have: import_zod4.z.number(),
720
+ need: import_zod4.z.number(),
721
+ surface: import_zod4.z.string().nullable()
722
+ }).nullable().describe("The nearest gap that would raise the confidence tier, when there is one")
659
723
  },
660
724
  annotations: {
661
725
  readOnlyHint: true,
@@ -664,7 +728,7 @@ function registerInsightTools(server, client) {
664
728
  }
665
729
  },
666
730
  withApiErrorGuidance(async ({ projectId }) => {
667
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
731
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
668
732
  const id = encodeURIComponent(projectId);
669
733
  const data = await client.get(`/projects/${id}/evidence-report`);
670
734
  const findings = (_a2 = data.findings) != null ? _a2 : [];
@@ -676,17 +740,23 @@ function registerInsightTools(server, client) {
676
740
  const emptyReason = (_d = data.emptyReason) != null ? _d : null;
677
741
  const lines = [];
678
742
  if (emptyReason) lines.push((_e = EMPTY_REASON_TEXT[emptyReason]) != null ? _e : emptyReason);
679
- if (isStale) lines.push("\u26A0 The analysis job has not run recently, so these numbers may have moved.");
743
+ if (isStale) lines.push("\u26A0 The analysis job has not run recently, so these numbers may have moved. Call refresh_insights to regenerate them.");
680
744
  if ((_f = data.interpretation) == null ? void 0 : _f.locked) {
681
745
  lines.push(`${data.interpretation.count || "Some"} recommendations are available on the Growth plan.`);
682
746
  } else if (((_g = data.interpretation) == null ? void 0 : _g.emptyReason) && !emptyReason) {
683
747
  lines.push(`Recommendations: ${(_h = EMPTY_REASON_TEXT[data.interpretation.emptyReason]) != null ? _h : data.interpretation.emptyReason}`);
684
748
  }
685
749
  if (data.generatedAt) lines.push(`Generated: ${new Date(data.generatedAt).toUTCString()}`);
750
+ const nextUnlock = (_i = data.nextUnlock) != null ? _i : null;
751
+ if (nextUnlock) {
752
+ const remaining = nextUnlock.need - nextUnlock.have;
753
+ const where = nextUnlock.surface ? `On ${nextUnlock.surface}: ` : "";
754
+ lines.push(`${where}${remaining} more ${nextUnlock.needs} would unlock the next confidence level (${nextUnlock.tier}).`);
755
+ }
686
756
  if (measured.length) {
687
757
  lines.push("", "Findings:");
688
758
  for (const f of measured) {
689
- const cov = f.provenance.coverage !== null && f.provenance.coverage < 0.999 ? `, covering ${Math.round(f.provenance.coverage * 100)}% of visitors` : "";
759
+ const cov = f.provenance.coverage !== null && f.provenance.coverage < 0.999 ? `, covering ${f.provenance.coverage < 5e-3 ? "under 1%" : `${Math.round(f.provenance.coverage * 100)}%`} of visitors` : "";
690
760
  lines.push(`- [${f.tier}] ${f.headline} (${f.provenance.sample} ${f.provenance.denominatorLabel}${cov})`);
691
761
  }
692
762
  }
@@ -698,7 +768,7 @@ function registerInsightTools(server, client) {
698
768
  content: [{ type: "text", text: lines.join("\n").trim() || "No findings yet." }],
699
769
  structuredContent: {
700
770
  status: findings.length ? "ok" : "empty",
701
- reached: (_i = data.reached) != null ? _i : "observed",
771
+ reached: (_j = data.reached) != null ? _j : "observed",
702
772
  findings: findings.map((f) => ({
703
773
  tier: f.tier,
704
774
  kind: f.kind,
@@ -711,7 +781,8 @@ function registerInsightTools(server, client) {
711
781
  observations,
712
782
  recommendations,
713
783
  isStale,
714
- generatedAt: (_j = data.generatedAt) != null ? _j : null
784
+ generatedAt: (_k = data.generatedAt) != null ? _k : null,
785
+ nextUnlock
715
786
  }
716
787
  };
717
788
  })
@@ -785,8 +856,8 @@ function registerGoalTools(server, client) {
785
856
  "get_goal_funnel",
786
857
  {
787
858
  title: "Goal funnel",
788
- description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown. This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report.",
789
- inputSchema: { projectId: projectIdSchema },
859
+ description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown (default window: last 30 calendar days). This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report.",
860
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("30d")),
790
861
  _meta: uiMeta("goal-funnel"),
791
862
  outputSchema: {
792
863
  currency: import_zod6.z.string().describe("Project display currency (ISO-4217) for the revenue fields"),
@@ -807,7 +878,8 @@ function registerGoalTools(server, client) {
807
878
  })
808
879
  ).describe("Per-variant breakdown")
809
880
  })
810
- ).describe("Configured goals (empty if none)")
881
+ ).describe("Configured goals (empty if none)"),
882
+ window: windowOutputSchema
811
883
  },
812
884
  annotations: {
813
885
  readOnlyHint: true,
@@ -815,11 +887,12 @@ function registerGoalTools(server, client) {
815
887
  openWorldHint: false
816
888
  }
817
889
  },
818
- withApiErrorGuidance(async ({ projectId }) => {
890
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
819
891
  var _a2, _b;
820
892
  const id = encodeURIComponent(projectId);
821
- const data = await client.get(`/projects/${id}/goals`);
893
+ const data = await client.get(`/projects/${id}/goals${rangeQuery({ range, from, to })}`);
822
894
  const structuredContent = {
895
+ window: data.window,
823
896
  currency: (_a2 = data.currency) != null ? _a2 : "USD",
824
897
  goals: data.goals.map((g) => {
825
898
  var _a3, _b2, _c;
@@ -847,6 +920,7 @@ function registerGoalTools(server, client) {
847
920
  };
848
921
  }
849
922
  const currency = (_b = data.currency) != null ? _b : "USD";
923
+ const win = windowLine(data.window);
850
924
  const lines = data.goals.flatMap((g) => {
851
925
  var _a3;
852
926
  return [
@@ -855,7 +929,8 @@ function registerGoalTools(server, client) {
855
929
  ""
856
930
  ];
857
931
  });
858
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
932
+ const text = (win ? [win, "", ...lines] : lines).join("\n").trim();
933
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("goal-funnel") };
859
934
  })
860
935
  );
861
936
  server.registerTool(
@@ -1699,8 +1774,12 @@ function registerAgentTrafficTools(server, client) {
1699
1774
  "get_agent_traffic",
1700
1775
  {
1701
1776
  title: "Agent traffic",
1702
- description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Agent traffic is tracked separately and never counted in conversion rate.",
1703
- inputSchema: { projectId: projectIdSchema },
1777
+ description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Defaults to the last 30 days; pass from/to for a custom period (agent logs are retained 30 days on Free/Starter, 90 on Growth+). Agent traffic is tracked separately and never counted in conversion rate.",
1778
+ inputSchema: {
1779
+ projectId: projectIdSchema,
1780
+ from: import_zod14.z.string().optional().describe("Period start (ISO 8601 date or timestamp). Defaults to 30 days ago."),
1781
+ to: import_zod14.z.string().optional().describe("Period end (ISO 8601 date or timestamp). Defaults to now.")
1782
+ },
1704
1783
  outputSchema: {
1705
1784
  totals: import_zod14.z.object({ crawler: import_zod14.z.number(), api: import_zod14.z.number(), browser: import_zod14.z.number() }),
1706
1785
  engines: import_zod14.z.array(
@@ -1718,9 +1797,14 @@ function registerAgentTrafficTools(server, client) {
1718
1797
  },
1719
1798
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
1720
1799
  },
1721
- withApiErrorGuidance(async ({ projectId }) => {
1800
+ withApiErrorGuidance(async ({ projectId, from, to }) => {
1722
1801
  const id = encodeURIComponent(projectId);
1723
- const s = await client.get(`/projects/${id}/agent-activity/summary`);
1802
+ const qs = new URLSearchParams();
1803
+ if (from) qs.set("from", from);
1804
+ if (to) qs.set("to", to);
1805
+ const s = await client.get(
1806
+ `/projects/${id}/agent-activity/summary${qs.size > 0 ? `?${qs.toString()}` : ""}`
1807
+ );
1724
1808
  const structuredContent = { totals: s.totals, engines: s.engines, intents: s.intents, topPaths: s.topPaths };
1725
1809
  const total = s.totals.crawler + s.totals.api + s.totals.browser;
1726
1810
  if (total === 0) {
@@ -1808,7 +1892,7 @@ function registerAgentTrafficTools(server, client) {
1808
1892
  }
1809
1893
 
1810
1894
  // src/server.ts
1811
- var PKG_VERSION = true ? "0.15.0" : "0.0.0-dev";
1895
+ var PKG_VERSION = true ? "0.17.0" : "0.0.0-dev";
1812
1896
  function createMcpServer(client) {
1813
1897
  const server = new import_mcp.McpServer(
1814
1898
  {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ApiClient,
4
4
  createMcpServer
5
- } from "./chunk-3RJUYHO7.js";
5
+ } from "./chunk-2E5M5ESK.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/dist/lib.cjs CHANGED
@@ -102,6 +102,44 @@ var ApiClient = class {
102
102
 
103
103
  // src/tools/common.ts
104
104
  var projectIdSchema = import_zod.z.string().uuid().describe("The project UUID");
105
+ function rangeInputSchema(defaultRange) {
106
+ return {
107
+ range: import_zod.z.enum(["7d", "30d", "90d", "all"]).optional().describe(
108
+ `Calendar window in the project's reporting timezone (default ${defaultRange}). 'all' means all retained data \u2014 the plan's retention window bounds every lookback.`
109
+ ),
110
+ from: import_zod.z.string().optional().describe(
111
+ "Custom window start (YYYY-MM-DD; overrides range). Must be within the plan retention window."
112
+ ),
113
+ to: import_zod.z.string().optional().describe("Custom window end (YYYY-MM-DD, inclusive; default today).")
114
+ };
115
+ }
116
+ function rangeQuery(args) {
117
+ const p = new URLSearchParams();
118
+ if (args.from) {
119
+ p.set("from", args.from);
120
+ if (args.to) p.set("to", args.to);
121
+ } else if (args.range) {
122
+ p.set("range", args.range);
123
+ }
124
+ const s = p.toString();
125
+ return s ? `?${s}` : "";
126
+ }
127
+ var windowOutputSchema = import_zod.z.object({
128
+ range: import_zod.z.string(),
129
+ start: import_zod.z.string().nullable(),
130
+ end: import_zod.z.string().nullable(),
131
+ timezone: import_zod.z.string(),
132
+ bucketUnit: import_zod.z.string(),
133
+ retentionDays: import_zod.z.number().describe("The plan's effective data retention in days"),
134
+ earliestAvailable: import_zod.z.string().describe("First date retention still holds data for (YYYY-MM-DD)")
135
+ }).optional().describe("The window the server actually used for every count in this response");
136
+ function windowLine(window) {
137
+ if (!window) return "";
138
+ if (window.start && window.end) {
139
+ return `Window: ${window.start.slice(0, 10)} to ${window.end.slice(0, 10)} (${window.timezone}, exclusive end)`;
140
+ }
141
+ return `Window: all retained data (${window.retentionDays}-day retention)`;
142
+ }
105
143
  function apiErrorGuidance(err, extra) {
106
144
  if (extra && Object.prototype.hasOwnProperty.call(extra, err.message)) {
107
145
  return extra[err.message];
@@ -116,6 +154,12 @@ function apiErrorGuidance(err, extra) {
116
154
  default:
117
155
  break;
118
156
  }
157
+ if (err.message === "range_out_of_retention") {
158
+ return "The requested window starts before this plan retains data \u2014 older events have been deleted by the retention purge and cannot be recovered. Ask for a more recent window, or upgrade the SentientUI plan for longer history (Growth keeps 180 days, Scale 365).";
159
+ }
160
+ if (err.message === "invalid_range") {
161
+ return "Invalid time window. Use range '7d', '30d', '90d' or 'all', or from/to as YYYY-MM-DD dates.";
162
+ }
119
163
  if (err.status === 402) {
120
164
  return `This feature requires a higher plan (${err.message}). Upgrade your SentientUI plan, then try again.`;
121
165
  }
@@ -417,7 +461,11 @@ var RENDER_JS = {
417
461
  "variant-performance": `
418
462
  window.__render = function(data){
419
463
  var variants = (data && data.variants) || [];
420
- el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, last 7 days vs prior 7';
464
+ var winText = 'current vs prior period';
465
+ if (data && data.window && data.window.start && data.window.end) {
466
+ winText = String(data.window.start).slice(0, 10) + ' to ' + String(data.window.end).slice(0, 10) + ' vs prior period';
467
+ }
468
+ el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, ' + winText;
421
469
  if (!variants.length) { showEmpty('No variant data available yet.'); return; }
422
470
  var max = variants.reduce(function(m,v){ return Math.max(m, v.currentCvr || 0); }, 0) || 1;
423
471
  var sorted = variants.slice().sort(function(a,b){ return (b.currentCvr||0) - (a.currentCvr||0); });
@@ -546,8 +594,8 @@ function registerComponentTools(server, client) {
546
594
  "list_components",
547
595
  {
548
596
  title: "List components",
549
- description: "List all adaptive components in a project with variant counts and impression totals.",
550
- inputSchema: { projectId: projectIdSchema },
597
+ description: "List all adaptive components in a project with variant counts and impression totals. Counts cover all retained data by default; pass range or from/to for a window.",
598
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("all")),
551
599
  outputSchema: {
552
600
  components: import_zod3.z.array(
553
601
  import_zod3.z.object({
@@ -556,7 +604,8 @@ function registerComponentTools(server, client) {
556
604
  impressions: import_zod3.z.number(),
557
605
  conversions: import_zod3.z.number()
558
606
  })
559
- ).describe("Adaptive components in the project (empty if none)")
607
+ ).describe("Adaptive components in the project (empty if none)"),
608
+ window: windowOutputSchema
560
609
  },
561
610
  annotations: {
562
611
  readOnlyHint: true,
@@ -564,16 +613,17 @@ function registerComponentTools(server, client) {
564
613
  openWorldHint: false
565
614
  }
566
615
  },
567
- withApiErrorGuidance(async ({ projectId }) => {
616
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
568
617
  const id = encodeURIComponent(projectId);
569
- const { components } = await client.get(`/projects/${id}/components`);
618
+ const { components, window } = await client.get(`/projects/${id}/components${rangeQuery({ range, from, to })}`);
570
619
  const structuredContent = {
571
620
  components: components.map((c) => ({
572
621
  componentId: c.component_id,
573
622
  variantCount: c.variants.length,
574
623
  impressions: c.total_impressions,
575
624
  conversions: c.total_conversions
576
- }))
625
+ })),
626
+ window
577
627
  };
578
628
  if (!components.length) {
579
629
  return {
@@ -591,19 +641,20 @@ function registerComponentTools(server, client) {
591
641
  "get_variant_performance",
592
642
  {
593
643
  title: "Variant performance",
594
- description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
595
- inputSchema: { projectId: projectIdSchema },
644
+ description: "Get CVR and momentum for all variants in a project over the selected window vs the immediately-preceding window of equal length (default: last 7 calendar days vs prior 7).",
645
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("7d")),
596
646
  _meta: uiMeta("variant-performance"),
597
647
  outputSchema: {
598
648
  variants: import_zod3.z.array(
599
649
  import_zod3.z.object({
600
650
  variantId: import_zod3.z.string(),
601
- currentCvr: import_zod3.z.number().describe("Conversion rate over the last 7 days (0-1)"),
602
- priorCvr: import_zod3.z.number().describe("Conversion rate over the prior 7 days (0-1)"),
651
+ currentCvr: import_zod3.z.number().describe("Conversion rate over the selected window (0-1)"),
652
+ priorCvr: import_zod3.z.number().describe("Conversion rate over the preceding window (0-1)"),
603
653
  deltaPp: import_zod3.z.number().describe("Change in percentage points"),
604
654
  momentum: import_zod3.z.string().describe("Momentum direction: gaining, losing, or stable")
605
655
  })
606
- ).describe("Per-variant performance (empty if no data yet)")
656
+ ).describe("Per-variant performance (empty if no data yet)"),
657
+ window: windowOutputSchema
607
658
  },
608
659
  annotations: {
609
660
  readOnlyHint: true,
@@ -611,10 +662,10 @@ function registerComponentTools(server, client) {
611
662
  openWorldHint: false
612
663
  }
613
664
  },
614
- withApiErrorGuidance(async ({ projectId }) => {
665
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
615
666
  var _a, _b, _c;
616
667
  const id = encodeURIComponent(projectId);
617
- const data = await client.get(`/projects/${id}/trends`);
668
+ const data = await client.get(`/projects/${id}/trends${rangeQuery({ range, from, to })}`);
618
669
  const momentumMap = new Map(((_a = data.momentum) != null ? _a : []).map((m) => [m.variantId, m.direction]));
619
670
  const structuredContent = {
620
671
  variants: ((_b = data.cvr) != null ? _b : []).map((v) => {
@@ -626,7 +677,8 @@ function registerComponentTools(server, client) {
626
677
  deltaPp: v.deltaPp,
627
678
  momentum: (_a2 = momentumMap.get(v.variantId)) != null ? _a2 : "stable"
628
679
  };
629
- })
680
+ }),
681
+ window: data.window
630
682
  };
631
683
  if (!((_c = data.cvr) == null ? void 0 : _c.length)) {
632
684
  return {
@@ -635,12 +687,14 @@ function registerComponentTools(server, client) {
635
687
  _meta: uiMeta("variant-performance")
636
688
  };
637
689
  }
638
- const text = data.cvr.map(
690
+ const lines = data.cvr.map(
639
691
  (v) => {
640
692
  var _a2;
641
693
  return `- ${v.variantId}: CVR ${(v.currentCvr * 100).toFixed(2)}% (prior ${(v.priorCvr * 100).toFixed(2)}%, ${v.deltaPp > 0 ? "+" : ""}${v.deltaPp.toFixed(1)} pp, ${(_a2 = momentumMap.get(v.variantId)) != null ? _a2 : "stable"})`;
642
694
  }
643
- ).join("\n");
695
+ );
696
+ const win = windowLine(data.window);
697
+ const text = (win ? [win, ...lines] : lines).join("\n");
644
698
  return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
645
699
  })
646
700
  );
@@ -654,8 +708,11 @@ var EMPTY_REASON_TEXT = {
654
708
  awaiting_profiles: "Still building visitor profiles before visitors can be grouped.",
655
709
  no_variants: "Nothing to compare yet \u2014 no adaptive component is running.",
656
710
  nothing_moved: "Enough data, and nothing moved this period. That is a result.",
657
- job_never_ran: "The analysis job has not run yet.",
658
- job_stale: "These numbers are out of date \u2014 the analysis job has not run recently."
711
+ // The stale/never-ran texts name the remedy: an agent that is not told about
712
+ // refresh_insights has no way to know it can fix this itself.
713
+ job_never_ran: "The analysis job has not run yet. Call refresh_insights to run it now.",
714
+ job_stale: "These numbers are out of date \u2014 the analysis job has not run recently. Call refresh_insights to regenerate them.",
715
+ data_unavailable: "The project data could not be read just now \u2014 nothing is wrong with the setup. Retry shortly."
659
716
  };
660
717
  function registerInsightTools(server, client) {
661
718
  server.registerTool(
@@ -679,7 +736,14 @@ function registerInsightTools(server, client) {
679
736
  observations: import_zod4.z.array(import_zod4.z.string()).describe("Headlines of measured findings"),
680
737
  recommendations: import_zod4.z.array(import_zod4.z.string()).describe("Headlines of interpreted findings (Growth tier)"),
681
738
  isStale: import_zod4.z.boolean().describe("True when the analysis has not run recently"),
682
- generatedAt: import_zod4.z.string().nullable().describe("ISO timestamp, or null")
739
+ generatedAt: import_zod4.z.string().nullable().describe("ISO timestamp, or null"),
740
+ nextUnlock: import_zod4.z.object({
741
+ tier: import_zod4.z.string(),
742
+ needs: import_zod4.z.string(),
743
+ have: import_zod4.z.number(),
744
+ need: import_zod4.z.number(),
745
+ surface: import_zod4.z.string().nullable()
746
+ }).nullable().describe("The nearest gap that would raise the confidence tier, when there is one")
683
747
  },
684
748
  annotations: {
685
749
  readOnlyHint: true,
@@ -688,7 +752,7 @@ function registerInsightTools(server, client) {
688
752
  }
689
753
  },
690
754
  withApiErrorGuidance(async ({ projectId }) => {
691
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
755
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
692
756
  const id = encodeURIComponent(projectId);
693
757
  const data = await client.get(`/projects/${id}/evidence-report`);
694
758
  const findings = (_a = data.findings) != null ? _a : [];
@@ -700,17 +764,23 @@ function registerInsightTools(server, client) {
700
764
  const emptyReason = (_d = data.emptyReason) != null ? _d : null;
701
765
  const lines = [];
702
766
  if (emptyReason) lines.push((_e = EMPTY_REASON_TEXT[emptyReason]) != null ? _e : emptyReason);
703
- if (isStale) lines.push("\u26A0 The analysis job has not run recently, so these numbers may have moved.");
767
+ if (isStale) lines.push("\u26A0 The analysis job has not run recently, so these numbers may have moved. Call refresh_insights to regenerate them.");
704
768
  if ((_f = data.interpretation) == null ? void 0 : _f.locked) {
705
769
  lines.push(`${data.interpretation.count || "Some"} recommendations are available on the Growth plan.`);
706
770
  } else if (((_g = data.interpretation) == null ? void 0 : _g.emptyReason) && !emptyReason) {
707
771
  lines.push(`Recommendations: ${(_h = EMPTY_REASON_TEXT[data.interpretation.emptyReason]) != null ? _h : data.interpretation.emptyReason}`);
708
772
  }
709
773
  if (data.generatedAt) lines.push(`Generated: ${new Date(data.generatedAt).toUTCString()}`);
774
+ const nextUnlock = (_i = data.nextUnlock) != null ? _i : null;
775
+ if (nextUnlock) {
776
+ const remaining = nextUnlock.need - nextUnlock.have;
777
+ const where = nextUnlock.surface ? `On ${nextUnlock.surface}: ` : "";
778
+ lines.push(`${where}${remaining} more ${nextUnlock.needs} would unlock the next confidence level (${nextUnlock.tier}).`);
779
+ }
710
780
  if (measured.length) {
711
781
  lines.push("", "Findings:");
712
782
  for (const f of measured) {
713
- const cov = f.provenance.coverage !== null && f.provenance.coverage < 0.999 ? `, covering ${Math.round(f.provenance.coverage * 100)}% of visitors` : "";
783
+ const cov = f.provenance.coverage !== null && f.provenance.coverage < 0.999 ? `, covering ${f.provenance.coverage < 5e-3 ? "under 1%" : `${Math.round(f.provenance.coverage * 100)}%`} of visitors` : "";
714
784
  lines.push(`- [${f.tier}] ${f.headline} (${f.provenance.sample} ${f.provenance.denominatorLabel}${cov})`);
715
785
  }
716
786
  }
@@ -722,7 +792,7 @@ function registerInsightTools(server, client) {
722
792
  content: [{ type: "text", text: lines.join("\n").trim() || "No findings yet." }],
723
793
  structuredContent: {
724
794
  status: findings.length ? "ok" : "empty",
725
- reached: (_i = data.reached) != null ? _i : "observed",
795
+ reached: (_j = data.reached) != null ? _j : "observed",
726
796
  findings: findings.map((f) => ({
727
797
  tier: f.tier,
728
798
  kind: f.kind,
@@ -735,7 +805,8 @@ function registerInsightTools(server, client) {
735
805
  observations,
736
806
  recommendations,
737
807
  isStale,
738
- generatedAt: (_j = data.generatedAt) != null ? _j : null
808
+ generatedAt: (_k = data.generatedAt) != null ? _k : null,
809
+ nextUnlock
739
810
  }
740
811
  };
741
812
  })
@@ -809,8 +880,8 @@ function registerGoalTools(server, client) {
809
880
  "get_goal_funnel",
810
881
  {
811
882
  title: "Goal funnel",
812
- description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown. This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report.",
813
- inputSchema: { projectId: projectIdSchema },
883
+ description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown (default window: last 30 calendar days). This is a flat per-goal list \u2014 for multi-step funnel drop-off, use get_funnel_report.",
884
+ inputSchema: __spreadValues({ projectId: projectIdSchema }, rangeInputSchema("30d")),
814
885
  _meta: uiMeta("goal-funnel"),
815
886
  outputSchema: {
816
887
  currency: import_zod6.z.string().describe("Project display currency (ISO-4217) for the revenue fields"),
@@ -831,7 +902,8 @@ function registerGoalTools(server, client) {
831
902
  })
832
903
  ).describe("Per-variant breakdown")
833
904
  })
834
- ).describe("Configured goals (empty if none)")
905
+ ).describe("Configured goals (empty if none)"),
906
+ window: windowOutputSchema
835
907
  },
836
908
  annotations: {
837
909
  readOnlyHint: true,
@@ -839,11 +911,12 @@ function registerGoalTools(server, client) {
839
911
  openWorldHint: false
840
912
  }
841
913
  },
842
- withApiErrorGuidance(async ({ projectId }) => {
914
+ withApiErrorGuidance(async ({ projectId, range, from, to }) => {
843
915
  var _a, _b;
844
916
  const id = encodeURIComponent(projectId);
845
- const data = await client.get(`/projects/${id}/goals`);
917
+ const data = await client.get(`/projects/${id}/goals${rangeQuery({ range, from, to })}`);
846
918
  const structuredContent = {
919
+ window: data.window,
847
920
  currency: (_a = data.currency) != null ? _a : "USD",
848
921
  goals: data.goals.map((g) => {
849
922
  var _a2, _b2, _c;
@@ -871,6 +944,7 @@ function registerGoalTools(server, client) {
871
944
  };
872
945
  }
873
946
  const currency = (_b = data.currency) != null ? _b : "USD";
947
+ const win = windowLine(data.window);
874
948
  const lines = data.goals.flatMap((g) => {
875
949
  var _a2;
876
950
  return [
@@ -879,7 +953,8 @@ function registerGoalTools(server, client) {
879
953
  ""
880
954
  ];
881
955
  });
882
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
956
+ const text = (win ? [win, "", ...lines] : lines).join("\n").trim();
957
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("goal-funnel") };
883
958
  })
884
959
  );
885
960
  server.registerTool(
@@ -1723,8 +1798,12 @@ function registerAgentTrafficTools(server, client) {
1723
1798
  "get_agent_traffic",
1724
1799
  {
1725
1800
  title: "Agent traffic",
1726
- description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Agent traffic is tracked separately and never counted in conversion rate.",
1727
- inputSchema: { projectId: projectIdSchema },
1801
+ description: "Which AI agents and crawlers are reading this site: totals by type (passive crawlers, agentic browsers, agent API calls), engine breakdown, and the paths they fetch most. Defaults to the last 30 days; pass from/to for a custom period (agent logs are retained 30 days on Free/Starter, 90 on Growth+). Agent traffic is tracked separately and never counted in conversion rate.",
1802
+ inputSchema: {
1803
+ projectId: projectIdSchema,
1804
+ from: import_zod14.z.string().optional().describe("Period start (ISO 8601 date or timestamp). Defaults to 30 days ago."),
1805
+ to: import_zod14.z.string().optional().describe("Period end (ISO 8601 date or timestamp). Defaults to now.")
1806
+ },
1728
1807
  outputSchema: {
1729
1808
  totals: import_zod14.z.object({ crawler: import_zod14.z.number(), api: import_zod14.z.number(), browser: import_zod14.z.number() }),
1730
1809
  engines: import_zod14.z.array(
@@ -1742,9 +1821,14 @@ function registerAgentTrafficTools(server, client) {
1742
1821
  },
1743
1822
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
1744
1823
  },
1745
- withApiErrorGuidance(async ({ projectId }) => {
1824
+ withApiErrorGuidance(async ({ projectId, from, to }) => {
1746
1825
  const id = encodeURIComponent(projectId);
1747
- const s = await client.get(`/projects/${id}/agent-activity/summary`);
1826
+ const qs = new URLSearchParams();
1827
+ if (from) qs.set("from", from);
1828
+ if (to) qs.set("to", to);
1829
+ const s = await client.get(
1830
+ `/projects/${id}/agent-activity/summary${qs.size > 0 ? `?${qs.toString()}` : ""}`
1831
+ );
1748
1832
  const structuredContent = { totals: s.totals, engines: s.engines, intents: s.intents, topPaths: s.topPaths };
1749
1833
  const total = s.totals.crawler + s.totals.api + s.totals.browser;
1750
1834
  if (total === 0) {
@@ -1832,7 +1916,7 @@ function registerAgentTrafficTools(server, client) {
1832
1916
  }
1833
1917
 
1834
1918
  // src/server.ts
1835
- var PKG_VERSION = true ? "0.15.0" : "0.0.0-dev";
1919
+ var PKG_VERSION = true ? "0.17.0" : "0.0.0-dev";
1836
1920
  function createMcpServer(client) {
1837
1921
  const server = new import_mcp.McpServer(
1838
1922
  {
package/dist/lib.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ApiError,
5
5
  PUBLIC_UI_RESOURCE_URIS,
6
6
  createMcpServer
7
- } from "./chunk-3RJUYHO7.js";
7
+ } from "./chunk-2E5M5ESK.js";
8
8
  export {
9
9
  ApiClient,
10
10
  ApiError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/mcp",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "MCP server for SentientUI — exposes project data and actions to AI agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentient-ui.com",