@sentientui/mcp 0.7.0 → 0.8.1

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.
@@ -7,16 +7,15 @@ var ApiError = class extends Error {
7
7
  this.status = status;
8
8
  this.name = "ApiError";
9
9
  }
10
- status;
11
10
  };
12
11
  var ApiClient = class {
13
- baseUrl;
14
- apiKey;
15
12
  constructor(opts) {
13
+ var _a;
16
14
  this.apiKey = opts.apiKey;
17
- this.baseUrl = (opts.baseUrl ?? "https://api.sentient-ui.com").replace(/\/$/, "");
15
+ this.baseUrl = ((_a = opts.baseUrl) != null ? _a : "https://api.sentient-ui.com").replace(/\/$/, "");
18
16
  }
19
17
  async get(path) {
18
+ var _a;
20
19
  const res = await fetch(`${this.baseUrl}/v1/mgmt${path}`, {
21
20
  headers: {
22
21
  authorization: `Bearer ${this.apiKey}`,
@@ -25,11 +24,12 @@ var ApiClient = class {
25
24
  });
26
25
  if (!res.ok) {
27
26
  const body = await res.json().catch(() => ({}));
28
- throw new ApiError(res.status, String(body.error ?? res.statusText));
27
+ throw new ApiError(res.status, String((_a = body.error) != null ? _a : res.statusText));
29
28
  }
30
29
  return res.json();
31
30
  }
32
31
  async post(path, body) {
32
+ var _a;
33
33
  const res = await fetch(`${this.baseUrl}/v1/mgmt${path}`, {
34
34
  method: "POST",
35
35
  headers: {
@@ -40,7 +40,7 @@ var ApiClient = class {
40
40
  });
41
41
  if (!res.ok) {
42
42
  const errBody = await res.json().catch(() => ({}));
43
- throw new ApiError(res.status, String(errBody.error ?? res.statusText));
43
+ throw new ApiError(res.status, String((_a = errBody.error) != null ? _a : res.statusText));
44
44
  }
45
45
  return res.json();
46
46
  }
@@ -51,8 +51,47 @@ import { createRequire } from "module";
51
51
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
52
52
 
53
53
  // src/tools/projects.ts
54
+ import { z as z2 } from "zod";
55
+
56
+ // src/tools/common.ts
54
57
  import { z } from "zod";
55
58
  var projectIdSchema = z.string().uuid().describe("The project UUID");
59
+ function apiErrorGuidance(err) {
60
+ switch (err.message) {
61
+ case "insufficient_scope":
62
+ return "This action needs an account login. Connect via the hosted MCP URL (https://api.sentient-ui.com/mcp) and sign in \u2014 a project-scoped server key (sk_\u2026) or anonymous demo token cannot do this.";
63
+ case "demo_read_only":
64
+ return "Demo mode is read-only. Create a SentientUI account and sign in (or use a project server key) to make changes.";
65
+ case "insufficient_role":
66
+ return "Your account role does not permit this action \u2014 it needs the account owner or an admin.";
67
+ default:
68
+ break;
69
+ }
70
+ if (err.status === 402) {
71
+ return `This feature requires a higher plan (${err.message}). Upgrade your SentientUI plan, then try again.`;
72
+ }
73
+ if (err.status === 403) {
74
+ return `Access denied (${err.message}). Check that your key or login has access to this project.`;
75
+ }
76
+ return null;
77
+ }
78
+ function withApiErrorGuidance(fn) {
79
+ return async (args) => {
80
+ try {
81
+ return await fn(args);
82
+ } catch (err) {
83
+ if (err instanceof ApiError) {
84
+ const guidance = apiErrorGuidance(err);
85
+ if (guidance) {
86
+ return { content: [{ type: "text", text: guidance }], isError: true };
87
+ }
88
+ }
89
+ throw err;
90
+ }
91
+ };
92
+ }
93
+
94
+ // src/tools/projects.ts
56
95
  function createProjectGuidance(err) {
57
96
  switch (err.message) {
58
97
  case "insufficient_scope":
@@ -76,16 +115,16 @@ function registerProjectTools(server, client) {
76
115
  title: "Create project",
77
116
  description: "Create a NEW SentientUI project (onboarding). Returns the project id and its pk_ public key for the SDK. Requires an account login: this works when connected via OAuth (the hosted MCP URL) but NOT with a project-scoped sk_ server key or an anonymous demo token. After it succeeds, call get_integration_guide and help the user install @sentientui/react with the returned key.",
78
117
  inputSchema: {
79
- name: z.string().min(1).describe("Human-readable project name"),
80
- contextType: z.enum(["saas", "ecommerce", "marketing", "internal"]).optional().describe("What kind of product this is; defaults to saas"),
81
- framework: z.enum(["next", "react", "core"]).optional().describe("How the site is built \u2014 next, react, or core (website builder/CMS); defaults to next"),
82
- websiteUrl: z.string().optional().describe("Production site origin to allow-list so the SDK's events aren't origin-blocked on day one")
118
+ name: z2.string().min(1).describe("Human-readable project name"),
119
+ contextType: z2.enum(["saas", "ecommerce", "marketing", "landing", "internal"]).optional().describe("What kind of product this is; defaults to saas"),
120
+ framework: z2.enum(["next", "react", "core"]).optional().describe("How the site is built \u2014 next, react, or core (website builder/CMS); defaults to next"),
121
+ websiteUrl: z2.string().optional().describe("Production site origin to allow-list so the SDK's events aren't origin-blocked on day one")
83
122
  },
84
123
  outputSchema: {
85
- projectId: z.string().describe("The new project UUID"),
86
- publicKey: z.string().describe("The pk_ public key to configure the SDK with"),
87
- name: z.string().describe("The project name"),
88
- contextType: z.string().describe("The resolved context type")
124
+ projectId: z2.string().describe("The new project UUID"),
125
+ publicKey: z2.string().describe("The pk_ public key to configure the SDK with"),
126
+ name: z2.string().describe("The project name"),
127
+ contextType: z2.string().describe("The resolved context type")
89
128
  },
90
129
  annotations: {
91
130
  readOnlyHint: false,
@@ -102,7 +141,7 @@ function registerProjectTools(server, client) {
102
141
  framework,
103
142
  origin: websiteUrl
104
143
  });
105
- const resolvedContextType = contextType ?? "saas";
144
+ const resolvedContextType = contextType != null ? contextType : "saas";
106
145
  return {
107
146
  content: [{
108
147
  type: "text",
@@ -137,12 +176,12 @@ function registerProjectTools(server, client) {
137
176
  description: "List all SentientUI projects for the authenticated account.",
138
177
  inputSchema: {},
139
178
  outputSchema: {
140
- projects: z.array(
141
- z.object({
142
- id: z.string().describe("Project UUID"),
143
- name: z.string(),
144
- contextType: z.string(),
145
- createdAt: z.string().describe("ISO date (YYYY-MM-DD)")
179
+ projects: z2.array(
180
+ z2.object({
181
+ id: z2.string().describe("Project UUID"),
182
+ name: z2.string(),
183
+ contextType: z2.string(),
184
+ createdAt: z2.string().describe("ISO date (YYYY-MM-DD)")
146
185
  })
147
186
  ).describe("All projects for the account (empty if none)")
148
187
  },
@@ -152,7 +191,7 @@ function registerProjectTools(server, client) {
152
191
  openWorldHint: false
153
192
  }
154
193
  },
155
- async () => {
194
+ withApiErrorGuidance(async () => {
156
195
  const projects = await client.get("/projects");
157
196
  const text = projects.length === 0 ? "No projects found." : projects.map(
158
197
  (p) => `- ${p.name} (id: ${p.id}, type: ${p.context_type}, created: ${p.created_at.slice(0, 10)})`
@@ -168,7 +207,7 @@ function registerProjectTools(server, client) {
168
207
  }))
169
208
  }
170
209
  };
171
- }
210
+ })
172
211
  );
173
212
  server.registerTool(
174
213
  "get_project_stats",
@@ -177,11 +216,11 @@ function registerProjectTools(server, client) {
177
216
  description: "Get health stats for a project: event volume, session count, agent calls, and status.",
178
217
  inputSchema: { projectId: projectIdSchema },
179
218
  outputSchema: {
180
- status: z.string().describe("Overall project health status"),
181
- events24h: z.number().describe("Events in the last 24 hours"),
182
- sessions24h: z.number().describe("Sessions in the last 24 hours"),
183
- agentCalls: z.number().describe("Total agent (MCP/API) calls"),
184
- lastEventAt: z.string().nullable().describe("ISO timestamp of the last event, or null")
219
+ status: z2.string().describe("Overall project health status"),
220
+ events24h: z2.number().describe("Events in the last 24 hours"),
221
+ sessions24h: z2.number().describe("Sessions in the last 24 hours"),
222
+ agentCalls: z2.number().describe("Total agent (MCP/API) calls"),
223
+ lastEventAt: z2.string().nullable().describe("ISO timestamp of the last event, or null")
185
224
  },
186
225
  annotations: {
187
226
  readOnlyHint: true,
@@ -189,7 +228,8 @@ function registerProjectTools(server, client) {
189
228
  openWorldHint: false
190
229
  }
191
230
  },
192
- async ({ projectId }) => {
231
+ withApiErrorGuidance(async ({ projectId }) => {
232
+ var _a;
193
233
  const id = encodeURIComponent(projectId);
194
234
  const stats = await client.get(`/projects/${id}/health`);
195
235
  const text = [
@@ -197,7 +237,7 @@ function registerProjectTools(server, client) {
197
237
  `Events (24h): ${stats.events24h}`,
198
238
  `Sessions (24h): ${stats.sessions24h}`,
199
239
  `Agent calls (total): ${stats.agentCalls}`,
200
- `Last event: ${stats.lastEventAt ?? "never"}`
240
+ `Last event: ${(_a = stats.lastEventAt) != null ? _a : "never"}`
201
241
  ].join("\n");
202
242
  return {
203
243
  content: [{ type: "text", text }],
@@ -209,12 +249,12 @@ function registerProjectTools(server, client) {
209
249
  lastEventAt: stats.lastEventAt
210
250
  }
211
251
  };
212
- }
252
+ })
213
253
  );
214
254
  }
215
255
 
216
256
  // src/tools/components.ts
217
- import { z as z2 } from "zod";
257
+ import { z as z3 } from "zod";
218
258
 
219
259
  // src/ui/templates.ts
220
260
  var VIZ_TITLES = {
@@ -473,21 +513,20 @@ function registerUiResources(server) {
473
513
  }
474
514
 
475
515
  // src/tools/components.ts
476
- var projectIdSchema2 = z2.string().uuid().describe("The project UUID");
477
516
  function registerComponentTools(server, client) {
478
517
  server.registerTool(
479
518
  "list_components",
480
519
  {
481
520
  title: "List components",
482
521
  description: "List all adaptive components in a project with variant counts and impression totals.",
483
- inputSchema: { projectId: projectIdSchema2 },
522
+ inputSchema: { projectId: projectIdSchema },
484
523
  outputSchema: {
485
- components: z2.array(
486
- z2.object({
487
- componentId: z2.string(),
488
- variantCount: z2.number(),
489
- impressions: z2.number(),
490
- conversions: z2.number()
524
+ components: z3.array(
525
+ z3.object({
526
+ componentId: z3.string(),
527
+ variantCount: z3.number(),
528
+ impressions: z3.number(),
529
+ conversions: z3.number()
491
530
  })
492
531
  ).describe("Adaptive components in the project (empty if none)")
493
532
  },
@@ -497,7 +536,7 @@ function registerComponentTools(server, client) {
497
536
  openWorldHint: false
498
537
  }
499
538
  },
500
- async ({ projectId }) => {
539
+ withApiErrorGuidance(async ({ projectId }) => {
501
540
  const id = encodeURIComponent(projectId);
502
541
  const { components } = await client.get(`/projects/${id}/components`);
503
542
  const structuredContent = {
@@ -518,23 +557,23 @@ function registerComponentTools(server, client) {
518
557
  (c) => `- ${c.component_id}: ${c.variants.length} variants, ${c.total_impressions} impressions, ${c.total_conversions} conversions`
519
558
  ).join("\n");
520
559
  return { content: [{ type: "text", text }], structuredContent };
521
- }
560
+ })
522
561
  );
523
562
  server.registerTool(
524
563
  "get_variant_performance",
525
564
  {
526
565
  title: "Variant performance",
527
566
  description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
528
- inputSchema: { projectId: projectIdSchema2 },
567
+ inputSchema: { projectId: projectIdSchema },
529
568
  _meta: uiMeta("variant-performance"),
530
569
  outputSchema: {
531
- variants: z2.array(
532
- z2.object({
533
- variantId: z2.string(),
534
- currentCvr: z2.number().describe("Conversion rate over the last 7 days (0-1)"),
535
- priorCvr: z2.number().describe("Conversion rate over the prior 7 days (0-1)"),
536
- deltaPp: z2.number().describe("Change in percentage points"),
537
- momentum: z2.string().describe("Momentum direction: gaining, losing, or stable")
570
+ variants: z3.array(
571
+ z3.object({
572
+ variantId: z3.string(),
573
+ currentCvr: z3.number().describe("Conversion rate over the last 7 days (0-1)"),
574
+ priorCvr: z3.number().describe("Conversion rate over the prior 7 days (0-1)"),
575
+ deltaPp: z3.number().describe("Change in percentage points"),
576
+ momentum: z3.string().describe("Momentum direction: gaining, losing, or stable")
538
577
  })
539
578
  ).describe("Per-variant performance (empty if no data yet)")
540
579
  },
@@ -544,20 +583,24 @@ function registerComponentTools(server, client) {
544
583
  openWorldHint: false
545
584
  }
546
585
  },
547
- async ({ projectId }) => {
586
+ withApiErrorGuidance(async ({ projectId }) => {
587
+ var _a, _b, _c;
548
588
  const id = encodeURIComponent(projectId);
549
589
  const data = await client.get(`/projects/${id}/trends`);
550
- const momentumMap = new Map((data.momentum ?? []).map((m) => [m.variantId, m.direction]));
590
+ const momentumMap = new Map(((_a = data.momentum) != null ? _a : []).map((m) => [m.variantId, m.direction]));
551
591
  const structuredContent = {
552
- variants: (data.cvr ?? []).map((v) => ({
553
- variantId: v.variantId,
554
- currentCvr: v.currentCvr,
555
- priorCvr: v.priorCvr,
556
- deltaPp: v.deltaPp,
557
- momentum: momentumMap.get(v.variantId) ?? "stable"
558
- }))
592
+ variants: ((_b = data.cvr) != null ? _b : []).map((v) => {
593
+ var _a2;
594
+ return {
595
+ variantId: v.variantId,
596
+ currentCvr: v.currentCvr,
597
+ priorCvr: v.priorCvr,
598
+ deltaPp: v.deltaPp,
599
+ momentum: (_a2 = momentumMap.get(v.variantId)) != null ? _a2 : "stable"
600
+ };
601
+ })
559
602
  };
560
- if (!data.cvr?.length) {
603
+ if (!((_c = data.cvr) == null ? void 0 : _c.length)) {
561
604
  return {
562
605
  content: [{ type: "text", text: "No variant data available yet." }],
563
606
  structuredContent,
@@ -565,29 +608,31 @@ function registerComponentTools(server, client) {
565
608
  };
566
609
  }
567
610
  const text = data.cvr.map(
568
- (v) => `- ${v.variantId}: CVR ${(v.currentCvr * 100).toFixed(2)}% (prior ${(v.priorCvr * 100).toFixed(2)}%, ${v.deltaPp > 0 ? "+" : ""}${v.deltaPp.toFixed(1)} pp, ${momentumMap.get(v.variantId) ?? "stable"})`
611
+ (v) => {
612
+ var _a2;
613
+ 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"})`;
614
+ }
569
615
  ).join("\n");
570
616
  return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
571
- }
617
+ })
572
618
  );
573
619
  }
574
620
 
575
621
  // src/tools/insights.ts
576
- import { z as z3 } from "zod";
577
- var projectIdSchema3 = z3.string().uuid().describe("The project UUID");
622
+ import { z as z4 } from "zod";
578
623
  function registerInsightTools(server, client) {
579
624
  server.registerTool(
580
625
  "get_insights",
581
626
  {
582
627
  title: "Get insights",
583
628
  description: "Get the latest AI-generated insights: narrator observations and (Growth tier) advisor recommendations.",
584
- inputSchema: { projectId: projectIdSchema3 },
629
+ inputSchema: { projectId: projectIdSchema },
585
630
  outputSchema: {
586
- status: z3.enum(["ok", "empty"]).describe("Whether insights exist yet"),
587
- observations: z3.array(z3.string()).describe("Narrator observations"),
588
- recommendations: z3.array(z3.string()).describe("Advisor recommendations (Growth tier)"),
589
- isStale: z3.boolean().describe("True when the insights are older than ~6h"),
590
- generatedAt: z3.string().nullable().describe("ISO timestamp the insights were generated, or null")
631
+ status: z4.enum(["ok", "empty"]).describe("Whether insights exist yet"),
632
+ observations: z4.array(z4.string()).describe("Narrator observations"),
633
+ recommendations: z4.array(z4.string()).describe("Advisor recommendations (Growth tier)"),
634
+ isStale: z4.boolean().describe("True when the insights are older than ~6h"),
635
+ generatedAt: z4.string().nullable().describe("ISO timestamp the insights were generated, or null")
591
636
  },
592
637
  annotations: {
593
638
  readOnlyHint: true,
@@ -595,7 +640,8 @@ function registerInsightTools(server, client) {
595
640
  openWorldHint: false
596
641
  }
597
642
  },
598
- async ({ projectId }) => {
643
+ withApiErrorGuidance(async ({ projectId }) => {
644
+ var _a, _b, _c, _d;
599
645
  const id = encodeURIComponent(projectId);
600
646
  const data = await client.get(`/projects/${id}/insights`);
601
647
  if (data.status === "empty") {
@@ -610,8 +656,8 @@ function registerInsightTools(server, client) {
610
656
  }
611
657
  };
612
658
  }
613
- const observations = data.narratorBullets ?? [];
614
- const recommendations = data.advisorBullets ?? [];
659
+ const observations = (_a = data.narratorBullets) != null ? _a : [];
660
+ const recommendations = (_b = data.advisorBullets) != null ? _b : [];
615
661
  const lines = [];
616
662
  if (data.isStale) lines.push("\u26A0 Insights are stale (>6h old). Consider calling refresh_insights.");
617
663
  if (data.generatedAt) lines.push(`Generated: ${new Date(data.generatedAt).toUTCString()}`);
@@ -629,33 +675,32 @@ function registerInsightTools(server, client) {
629
675
  status: "ok",
630
676
  observations,
631
677
  recommendations,
632
- isStale: data.isStale ?? false,
633
- generatedAt: data.generatedAt ?? null
678
+ isStale: (_c = data.isStale) != null ? _c : false,
679
+ generatedAt: (_d = data.generatedAt) != null ? _d : null
634
680
  }
635
681
  };
636
- }
682
+ })
637
683
  );
638
684
  }
639
685
 
640
686
  // src/tools/personas.ts
641
- import { z as z4 } from "zod";
642
- var projectIdSchema4 = z4.string().uuid().describe("The project UUID");
687
+ import { z as z5 } from "zod";
643
688
  function registerPersonaTools(server, client) {
644
689
  server.registerTool(
645
690
  "get_persona_breakdown",
646
691
  {
647
692
  title: "Persona breakdown",
648
693
  description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
649
- inputSchema: { projectId: projectIdSchema4 },
694
+ inputSchema: { projectId: projectIdSchema },
650
695
  _meta: uiMeta("persona-breakdown"),
651
696
  outputSchema: {
652
- totalSessions: z4.number().describe("Total sessions across all clusters"),
653
- clusters: z4.array(
654
- z4.object({
655
- label: z4.string(),
656
- sessionCount: z4.number(),
657
- sharePct: z4.number().describe("Share of total traffic (0-100)"),
658
- reliability: z4.number().describe("Average cluster reliability (0-1)")
697
+ totalSessions: z5.number().describe("Total sessions across all clusters"),
698
+ clusters: z5.array(
699
+ z5.object({
700
+ label: z5.string(),
701
+ sessionCount: z5.number(),
702
+ sharePct: z5.number().describe("Share of total traffic (0-100)"),
703
+ reliability: z5.number().describe("Average cluster reliability (0-1)")
659
704
  })
660
705
  ).describe("Persona clusters (empty until enough visitor data)")
661
706
  },
@@ -665,7 +710,7 @@ function registerPersonaTools(server, client) {
665
710
  openWorldHint: false
666
711
  }
667
712
  },
668
- async ({ projectId }) => {
713
+ withApiErrorGuidance(async ({ projectId }) => {
669
714
  const id = encodeURIComponent(projectId);
670
715
  const data = await client.get(`/projects/${id}/portraits`);
671
716
  const structuredContent = {
@@ -694,33 +739,32 @@ function registerPersonaTools(server, client) {
694
739
  })
695
740
  ];
696
741
  return { content: [{ type: "text", text: lines.join("\n") }], structuredContent, _meta: uiMeta("persona-breakdown") };
697
- }
742
+ })
698
743
  );
699
744
  }
700
745
 
701
746
  // src/tools/goals.ts
702
- import { z as z5 } from "zod";
703
- var projectIdSchema5 = z5.string().uuid().describe("The project UUID");
747
+ import { z as z6 } from "zod";
704
748
  function registerGoalTools(server, client) {
705
749
  server.registerTool(
706
750
  "get_goal_funnel",
707
751
  {
708
752
  title: "Goal funnel",
709
753
  description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
710
- inputSchema: { projectId: projectIdSchema5 },
754
+ inputSchema: { projectId: projectIdSchema },
711
755
  _meta: uiMeta("goal-funnel"),
712
756
  outputSchema: {
713
- goals: z5.array(
714
- z5.object({
715
- goalName: z5.string(),
716
- hits: z5.number(),
717
- uniqueSessions: z5.number(),
718
- conversionRate: z5.number().describe("Unique-session conversion rate (0-1)"),
719
- variants: z5.array(
720
- z5.object({
721
- componentId: z5.string(),
722
- variantId: z5.string(),
723
- completionRate: z5.number().describe("Completion rate per assigned session (0-1)")
757
+ goals: z6.array(
758
+ z6.object({
759
+ goalName: z6.string(),
760
+ hits: z6.number(),
761
+ uniqueSessions: z6.number(),
762
+ conversionRate: z6.number().describe("Unique-session conversion rate (0-1)"),
763
+ variants: z6.array(
764
+ z6.object({
765
+ componentId: z6.string(),
766
+ variantId: z6.string(),
767
+ completionRate: z6.number().describe("Completion rate per assigned session (0-1)")
724
768
  })
725
769
  ).describe("Per-variant breakdown")
726
770
  })
@@ -732,7 +776,7 @@ function registerGoalTools(server, client) {
732
776
  openWorldHint: false
733
777
  }
734
778
  },
735
- async ({ projectId }) => {
779
+ withApiErrorGuidance(async ({ projectId }) => {
736
780
  const id = encodeURIComponent(projectId);
737
781
  const data = await client.get(`/projects/${id}/goals`);
738
782
  const structuredContent = {
@@ -761,26 +805,25 @@ function registerGoalTools(server, client) {
761
805
  ""
762
806
  ]);
763
807
  return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
764
- }
808
+ })
765
809
  );
766
810
  }
767
811
 
768
812
  // src/tools/guardrails.ts
769
- import { z as z6 } from "zod";
770
- var projectIdSchema6 = z6.string().uuid().describe("The project UUID");
813
+ import { z as z7 } from "zod";
771
814
  function registerGuardrailTools(server, client) {
772
815
  server.registerTool(
773
816
  "list_guardrail_events",
774
817
  {
775
818
  title: "List guardrail events",
776
819
  description: "List variants currently paused by the guardrail in the last 24 hours.",
777
- inputSchema: { projectId: projectIdSchema6 },
820
+ inputSchema: { projectId: projectIdSchema },
778
821
  outputSchema: {
779
- events: z6.array(
780
- z6.object({
781
- componentId: z6.string(),
782
- variantIds: z6.array(z6.string()).describe("Variants paused by the guardrail"),
783
- pausedAt: z6.string().nullable().describe("ISO timestamp the pause fired, or null")
822
+ events: z7.array(
823
+ z7.object({
824
+ componentId: z7.string(),
825
+ variantIds: z7.array(z7.string()).describe("Variants paused by the guardrail"),
826
+ pausedAt: z7.string().nullable().describe("ISO timestamp the pause fired, or null")
784
827
  })
785
828
  ).describe("Guardrail events in the last 24h (empty if none)")
786
829
  },
@@ -790,7 +833,7 @@ function registerGuardrailTools(server, client) {
790
833
  openWorldHint: false
791
834
  }
792
835
  },
793
- async ({ projectId }) => {
836
+ withApiErrorGuidance(async ({ projectId }) => {
794
837
  const id = encodeURIComponent(projectId);
795
838
  const data = await client.get(`/projects/${id}/guardrail-events`);
796
839
  const structuredContent = {
@@ -810,28 +853,27 @@ function registerGuardrailTools(server, client) {
810
853
  (e) => `- ${e.componentId}: variants [${e.variantIds.join(", ")}] paused${e.pausedAt ? ` at ${e.pausedAt}` : ""}`
811
854
  );
812
855
  return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
813
- }
856
+ })
814
857
  );
815
858
  }
816
859
 
817
860
  // src/tools/layout.ts
818
- import { z as z7 } from "zod";
819
- var projectIdSchema7 = z7.string().uuid().describe("The project UUID");
861
+ import { z as z8 } from "zod";
820
862
  function registerLayoutTools(server, client) {
821
863
  server.registerTool(
822
864
  "get_layout_stats",
823
865
  {
824
866
  title: "Layout stats",
825
867
  description: "Get per-persona section layout rankings and bandit reward weights.",
826
- inputSchema: { projectId: projectIdSchema7 },
868
+ inputSchema: { projectId: projectIdSchema },
827
869
  _meta: uiMeta("layout-stats"),
828
870
  outputSchema: {
829
- layouts: z7.array(
830
- z7.object({
831
- persona: z7.string(),
832
- layoutOrder: z7.array(z7.string()).describe("Ranked section order for this persona"),
833
- pulls: z7.number().describe("Number of times this arm was served"),
834
- avgReward: z7.number().describe("Average bandit reward weight")
871
+ layouts: z8.array(
872
+ z8.object({
873
+ persona: z8.string(),
874
+ layoutOrder: z8.array(z8.string()).describe("Ranked section order for this persona"),
875
+ pulls: z8.number().describe("Number of times this arm was served"),
876
+ avgReward: z8.number().describe("Average bandit reward weight")
835
877
  })
836
878
  ).describe("Per-persona layout rankings (empty until enough sessions)")
837
879
  },
@@ -841,7 +883,7 @@ function registerLayoutTools(server, client) {
841
883
  openWorldHint: false
842
884
  }
843
885
  },
844
- async ({ projectId }) => {
886
+ withApiErrorGuidance(async ({ projectId }) => {
845
887
  const id = encodeURIComponent(projectId);
846
888
  const stats = await client.get(`/projects/${id}/layout-stats`);
847
889
  const structuredContent = {
@@ -863,13 +905,12 @@ function registerLayoutTools(server, client) {
863
905
  (s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
864
906
  ).join("\n");
865
907
  return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("layout-stats") };
866
- }
908
+ })
867
909
  );
868
910
  }
869
911
 
870
912
  // src/tools/variants.ts
871
- import { z as z8 } from "zod";
872
- var projectIdSchema8 = z8.string().uuid().describe("The project UUID");
913
+ import { z as z9 } from "zod";
873
914
  function registerVariantWriteTools(server, client) {
874
915
  server.registerTool(
875
916
  "create_variant",
@@ -877,17 +918,17 @@ function registerVariantWriteTools(server, client) {
877
918
  title: "Create managed variant",
878
919
  description: "Create a NO-CODE managed text variant for a component (content stored in SentientUI, rendered by <AdaptiveText>). Use this ONLY for text-only variants the user wants without a code change. For variants that will live in the codebase (full components \u2014 copy, markup, styling), use get_variant_brief and write the variant in code instead; those auto-register on deploy and do not need create_variant. Requires a paid plan (server keys are Starter+; anonymous demo tokens are read-only).",
879
920
  inputSchema: {
880
- projectId: projectIdSchema8,
881
- componentId: z8.string().describe("The component ID to add a variant to"),
882
- displayName: z8.string().describe("Human-readable name for the new variant"),
883
- content: z8.string().optional().describe("The text content for this managed variant (rendered by <AdaptiveText>). Generate it from get_variant_brief context; omit only to create an empty placeholder to fill in from the dashboard.")
921
+ projectId: projectIdSchema,
922
+ componentId: z9.string().describe("The component ID to add a variant to"),
923
+ displayName: z9.string().describe("Human-readable name for the new variant"),
924
+ content: z9.string().optional().describe("The text content for this managed variant (rendered by <AdaptiveText>). Generate it from get_variant_brief context; omit only to create an empty placeholder to fill in from the dashboard.")
884
925
  },
885
926
  outputSchema: {
886
- variantId: z8.string().describe("The new variant ID"),
887
- displayName: z8.string(),
888
- componentId: z8.string(),
889
- state: z8.literal("draft").describe("New managed variants start in draft state"),
890
- hasContent: z8.boolean().describe("Whether text content was provided at creation")
927
+ variantId: z9.string().describe("The new variant ID"),
928
+ displayName: z9.string(),
929
+ componentId: z9.string(),
930
+ state: z9.literal("draft").describe("New managed variants start in draft state"),
931
+ hasContent: z9.boolean().describe("Whether text content was provided at creation")
891
932
  },
892
933
  annotations: {
893
934
  readOnlyHint: false,
@@ -896,7 +937,7 @@ function registerVariantWriteTools(server, client) {
896
937
  openWorldHint: false
897
938
  }
898
939
  },
899
- async ({ projectId, componentId, displayName, content }) => {
940
+ withApiErrorGuidance(async ({ projectId, componentId, displayName, content }) => {
900
941
  const id = encodeURIComponent(projectId);
901
942
  const result = await client.post(
902
943
  `/projects/${id}/variants`,
@@ -916,7 +957,7 @@ function registerVariantWriteTools(server, client) {
916
957
  hasContent: Boolean(content)
917
958
  }
918
959
  };
919
- }
960
+ })
920
961
  );
921
962
  server.registerTool(
922
963
  "pause_variant",
@@ -924,14 +965,14 @@ function registerVariantWriteTools(server, client) {
924
965
  title: "Pause variant",
925
966
  description: "Pause a variant, stopping traffic from being assigned to it.",
926
967
  inputSchema: {
927
- projectId: projectIdSchema8,
928
- componentId: z8.string().describe("The component ID"),
929
- variantId: z8.string().describe("The variant ID to pause")
968
+ projectId: projectIdSchema,
969
+ componentId: z9.string().describe("The component ID"),
970
+ variantId: z9.string().describe("The variant ID to pause")
930
971
  },
931
972
  outputSchema: {
932
- variantId: z8.string(),
933
- componentId: z8.string(),
934
- paused: z8.literal(true).describe("The variant is now paused")
973
+ variantId: z9.string(),
974
+ componentId: z9.string(),
975
+ paused: z9.literal(true).describe("The variant is now paused")
935
976
  },
936
977
  annotations: {
937
978
  readOnlyHint: false,
@@ -940,7 +981,7 @@ function registerVariantWriteTools(server, client) {
940
981
  openWorldHint: false
941
982
  }
942
983
  },
943
- async ({ projectId, componentId, variantId }) => {
984
+ withApiErrorGuidance(async ({ projectId, componentId, variantId }) => {
944
985
  const id = encodeURIComponent(projectId);
945
986
  await client.post(`/projects/${id}/variants/pause`, { componentId, variantId });
946
987
  return {
@@ -950,17 +991,17 @@ function registerVariantWriteTools(server, client) {
950
991
  }],
951
992
  structuredContent: { variantId, componentId, paused: true }
952
993
  };
953
- }
994
+ })
954
995
  );
955
996
  server.registerTool(
956
997
  "refresh_insights",
957
998
  {
958
999
  title: "Refresh insights",
959
1000
  description: "Trigger fresh AI insight generation for a project. Returns immediately; use get_insights in ~15 seconds to see results.",
960
- inputSchema: { projectId: projectIdSchema8 },
1001
+ inputSchema: { projectId: projectIdSchema },
961
1002
  outputSchema: {
962
- projectId: z8.string(),
963
- status: z8.literal("generating").describe("Generation has been triggered")
1003
+ projectId: z9.string(),
1004
+ status: z9.literal("generating").describe("Generation has been triggered")
964
1005
  },
965
1006
  annotations: {
966
1007
  readOnlyHint: false,
@@ -969,7 +1010,7 @@ function registerVariantWriteTools(server, client) {
969
1010
  openWorldHint: false
970
1011
  }
971
1012
  },
972
- async ({ projectId }) => {
1013
+ withApiErrorGuidance(async ({ projectId }) => {
973
1014
  const id = encodeURIComponent(projectId);
974
1015
  await client.post(`/projects/${id}/insights/refresh`);
975
1016
  return {
@@ -979,13 +1020,12 @@ function registerVariantWriteTools(server, client) {
979
1020
  }],
980
1021
  structuredContent: { projectId, status: "generating" }
981
1022
  };
982
- }
1023
+ })
983
1024
  );
984
1025
  }
985
1026
 
986
1027
  // src/tools/variant-brief.ts
987
- import { z as z9 } from "zod";
988
- var projectIdSchema9 = z9.string().uuid().describe("The project UUID");
1028
+ import { z as z10 } from "zod";
989
1029
  var GOAL_TARGET = 500;
990
1030
  var BEST_PRACTICE_PRIORS = {
991
1031
  ecommerce: [
@@ -1023,11 +1063,12 @@ var GENERIC_PRIORS = [
1023
1063
  "Add one credible proof point near the action."
1024
1064
  ];
1025
1065
  function priorsFor(contextType) {
1026
- return BEST_PRACTICE_PRIORS[contextType] ?? GENERIC_PRIORS;
1066
+ var _a;
1067
+ return (_a = BEST_PRACTICE_PRIORS[contextType]) != null ? _a : GENERIC_PRIORS;
1027
1068
  }
1028
1069
  function computeDataState(impressions, insights, avgReliability) {
1029
1070
  if (impressions === 0) return "empty";
1030
- const insightsReady = insights?.status === "ok" && !insights.isStale;
1071
+ const insightsReady = (insights == null ? void 0 : insights.status) === "ok" && !insights.isStale;
1031
1072
  const reliable = avgReliability === null || avgReliability >= 0.3;
1032
1073
  if (impressions < GOAL_TARGET || !insightsReady || !reliable) return "collecting";
1033
1074
  return "sufficient";
@@ -1045,7 +1086,7 @@ function guidanceFor(dataState, contextType) {
1045
1086
  async function settled(p) {
1046
1087
  try {
1047
1088
  return await p;
1048
- } catch {
1089
+ } catch (e) {
1049
1090
  return null;
1050
1091
  }
1051
1092
  }
@@ -1056,16 +1097,16 @@ function registerVariantBriefTools(server, client) {
1056
1097
  title: "Variant brief",
1057
1098
  description: "Get an insight-driven brief for creating a new CODE-NATIVE variant of a component. Returns current variant performance, audience, insights, a data-sufficiency assessment (with a best-practice fallback when there is no data yet), and step-by-step instructions for writing the variant in the customer's code. Use this instead of create_variant when the variant will live in the codebase.",
1058
1099
  inputSchema: {
1059
- projectId: projectIdSchema9,
1060
- componentId: z9.string().describe('The component ID to write a new variant for (matches <Adaptive id="...">).')
1100
+ projectId: projectIdSchema,
1101
+ componentId: z10.string().describe('The component ID to write a new variant for (matches <Adaptive id="...">).')
1061
1102
  },
1062
1103
  outputSchema: {
1063
- componentId: z9.string(),
1064
- contextType: z9.string().describe("The project's context type (or 'unknown')"),
1065
- dataState: z9.enum(["sufficient", "collecting", "empty"]).describe("Data-sufficiency assessment"),
1066
- existingVariantIds: z9.array(z9.string()).describe("Variant IDs already in use (do not reuse)"),
1067
- priors: z9.array(z9.string()).describe("Best-practice priors applied for this context type"),
1068
- markdown: z9.string().describe("The full variant brief in Markdown")
1104
+ componentId: z10.string(),
1105
+ contextType: z10.string().describe("The project's context type (or 'unknown')"),
1106
+ dataState: z10.enum(["sufficient", "collecting", "empty"]).describe("Data-sufficiency assessment"),
1107
+ existingVariantIds: z10.array(z10.string()).describe("Variant IDs already in use (do not reuse)"),
1108
+ priors: z10.array(z10.string()).describe("Best-practice priors applied for this context type"),
1109
+ markdown: z10.string().describe("The full variant brief in Markdown")
1069
1110
  },
1070
1111
  annotations: {
1071
1112
  readOnlyHint: true,
@@ -1073,7 +1114,8 @@ function registerVariantBriefTools(server, client) {
1073
1114
  openWorldHint: false
1074
1115
  }
1075
1116
  },
1076
- async ({ projectId, componentId }) => {
1117
+ withApiErrorGuidance(async ({ projectId, componentId }) => {
1118
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
1077
1119
  const id = encodeURIComponent(projectId);
1078
1120
  const [projects, componentsEnvelope, trends, portraits, insights] = await Promise.all([
1079
1121
  settled(client.get("/projects")),
@@ -1083,19 +1125,19 @@ function registerVariantBriefTools(server, client) {
1083
1125
  settled(client.get(`/projects/${id}/portraits`)),
1084
1126
  settled(client.get(`/projects/${id}/insights`))
1085
1127
  ]);
1086
- const project = projects?.find((p) => p.id === projectId) ?? null;
1087
- const contextType = project?.context_type ?? "unknown";
1088
- const components = componentsEnvelope?.components ?? [];
1089
- const component = components.find((c) => c.component_id === componentId) ?? null;
1090
- const impressions = component?.total_impressions ?? 0;
1091
- const conversions = component?.total_conversions ?? 0;
1128
+ const project = (_a = projects == null ? void 0 : projects.find((p) => p.id === projectId)) != null ? _a : null;
1129
+ const contextType = (_b = project == null ? void 0 : project.context_type) != null ? _b : "unknown";
1130
+ const components = (_c = componentsEnvelope == null ? void 0 : componentsEnvelope.components) != null ? _c : [];
1131
+ const component = (_d = components.find((c) => c.component_id === componentId)) != null ? _d : null;
1132
+ const impressions = (_e = component == null ? void 0 : component.total_impressions) != null ? _e : 0;
1133
+ const conversions = (_f = component == null ? void 0 : component.total_conversions) != null ? _f : 0;
1092
1134
  const componentCvr = impressions > 0 ? conversions / impressions * 100 : 0;
1093
- const existingVariantIds = component?.variants.map((v) => v.variant_id) ?? [];
1135
+ const existingVariantIds = (_g = component == null ? void 0 : component.variants.map((v) => v.variant_id)) != null ? _g : [];
1094
1136
  const variantIdSet = new Set(existingVariantIds);
1095
- const momentumMap = new Map((trends?.momentum ?? []).map((m) => [m.variantId, m.direction]));
1096
- const variantPerf = (trends?.cvr ?? []).filter((v) => variantIdSet.has(v.variantId));
1097
- const clusters = portraits?.clusters ?? [];
1098
- const totalSessions = portraits?.totalSessions ?? 0;
1137
+ const momentumMap = new Map(((_h = trends == null ? void 0 : trends.momentum) != null ? _h : []).map((m) => [m.variantId, m.direction]));
1138
+ const variantPerf = ((_i = trends == null ? void 0 : trends.cvr) != null ? _i : []).filter((v) => variantIdSet.has(v.variantId));
1139
+ const clusters = (_j = portraits == null ? void 0 : portraits.clusters) != null ? _j : [];
1140
+ const totalSessions = (_k = portraits == null ? void 0 : portraits.totalSessions) != null ? _k : 0;
1099
1141
  const avgReliability = clusters.length ? clusters.reduce((s, c) => s + c.avgReliability, 0) / clusters.length : null;
1100
1142
  const dataState = computeDataState(impressions, insights, avgReliability);
1101
1143
  const lines = [];
@@ -1120,7 +1162,7 @@ function registerVariantBriefTools(server, client) {
1120
1162
  lines.push("Current variant performance (7d vs prior 7d):");
1121
1163
  for (const v of variantPerf) {
1122
1164
  lines.push(
1123
- `- ${v.variantId}: ${(v.currentCvr * 100).toFixed(2)}% CVR (${v.deltaPp > 0 ? "+" : ""}${v.deltaPp.toFixed(1)} pp, ${momentumMap.get(v.variantId) ?? "stable"})`
1165
+ `- ${v.variantId}: ${(v.currentCvr * 100).toFixed(2)}% CVR (${v.deltaPp > 0 ? "+" : ""}${v.deltaPp.toFixed(1)} pp, ${(_l = momentumMap.get(v.variantId)) != null ? _l : "stable"})`
1124
1166
  );
1125
1167
  }
1126
1168
  lines.push("");
@@ -1135,8 +1177,8 @@ function registerVariantBriefTools(server, client) {
1135
1177
  }
1136
1178
  if (insights && insights.status === "ok") {
1137
1179
  if (insights.isStale) lines.push("\u26A0 Insights are stale (>6h). Consider refresh_insights for a fresher read.");
1138
- const narrator = insights.narratorBullets ?? [];
1139
- const advisor = insights.advisorBullets ?? [];
1180
+ const narrator = (_m = insights.narratorBullets) != null ? _m : [];
1181
+ const advisor = (_n = insights.advisorBullets) != null ? _n : [];
1140
1182
  if (narrator.length) {
1141
1183
  lines.push("Insights \u2014 observations:");
1142
1184
  narrator.forEach((b) => lines.push(`- ${b}`));
@@ -1176,17 +1218,16 @@ function registerVariantBriefTools(server, client) {
1176
1218
  markdown
1177
1219
  }
1178
1220
  };
1179
- }
1221
+ })
1180
1222
  );
1181
1223
  }
1182
1224
 
1183
1225
  // src/tools/test-brief.ts
1184
- import { z as z10 } from "zod";
1185
- var projectIdSchema10 = z10.string().uuid().describe("The project UUID");
1226
+ import { z as z11 } from "zod";
1186
1227
  async function settled2(p) {
1187
1228
  try {
1188
1229
  return await p;
1189
- } catch {
1230
+ } catch (e) {
1190
1231
  return null;
1191
1232
  }
1192
1233
  }
@@ -1197,14 +1238,14 @@ function registerTestBriefTools(server, client) {
1197
1238
  title: "Test brief",
1198
1239
  description: "Get a ready-to-paste test for a SentientUI-wrapped component, populated with the component's real variants and goals. This project uses @sentientui/react/testing. Use this so your tests force a specific variant/layout deterministically and never break when the optimizer serves a different version. Returns a React Testing Library example plus the URL-param recipe for E2E (Playwright/Cypress).",
1199
1240
  inputSchema: {
1200
- projectId: projectIdSchema10,
1201
- componentId: z10.string().describe('The component ID to write a test for (matches <Adaptive id="...">).')
1241
+ projectId: projectIdSchema,
1242
+ componentId: z11.string().describe('The component ID to write a test for (matches <Adaptive id="...">).')
1202
1243
  },
1203
1244
  outputSchema: {
1204
- componentId: z10.string(),
1205
- forcedVariantId: z10.string().describe("The non-control variant the example forces"),
1206
- goalName: z10.string().describe("The goal the example asserts fires"),
1207
- markdown: z10.string().describe("The full test brief in Markdown")
1245
+ componentId: z11.string(),
1246
+ forcedVariantId: z11.string().describe("The non-control variant the example forces"),
1247
+ goalName: z11.string().describe("The goal the example asserts fires"),
1248
+ markdown: z11.string().describe("The full test brief in Markdown")
1208
1249
  },
1209
1250
  annotations: {
1210
1251
  readOnlyHint: true,
@@ -1212,20 +1253,21 @@ function registerTestBriefTools(server, client) {
1212
1253
  openWorldHint: false
1213
1254
  }
1214
1255
  },
1215
- async ({ projectId, componentId }) => {
1256
+ withApiErrorGuidance(async ({ projectId, componentId }) => {
1257
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1216
1258
  const id = encodeURIComponent(projectId);
1217
1259
  const [componentsEnvelope, goalsRes] = await Promise.all([
1218
1260
  // mgmt API returns a paginated envelope: { components, total, page, limit }.
1219
1261
  settled2(client.get(`/projects/${id}/components`)),
1220
1262
  settled2(client.get(`/projects/${id}/goals`))
1221
1263
  ]);
1222
- const components = componentsEnvelope?.components ?? [];
1223
- const component = components.find((c) => c.component_id === componentId) ?? null;
1224
- const variantIds = component?.variants.map((v) => v.variant_id) ?? [];
1225
- const goals = Array.isArray(goalsRes) ? goalsRes : goalsRes?.goals ?? [];
1226
- const goalName = goals[0]?.goalName ?? "signup";
1227
- const controlId = variantIds[0] ?? "control";
1228
- const forcedId = variantIds.find((v) => v !== controlId) ?? "variant_b";
1264
+ const components = (_a = componentsEnvelope == null ? void 0 : componentsEnvelope.components) != null ? _a : [];
1265
+ const component = (_b = components.find((c) => c.component_id === componentId)) != null ? _b : null;
1266
+ const variantIds = (_c = component == null ? void 0 : component.variants.map((v) => v.variant_id)) != null ? _c : [];
1267
+ const goals = Array.isArray(goalsRes) ? goalsRes : (_d = goalsRes == null ? void 0 : goalsRes.goals) != null ? _d : [];
1268
+ const goalName = (_f = (_e = goals[0]) == null ? void 0 : _e.goalName) != null ? _f : "signup";
1269
+ const controlId = (_g = variantIds[0]) != null ? _g : "control";
1270
+ const forcedId = (_h = variantIds.find((v) => v !== controlId)) != null ? _h : "variant_b";
1229
1271
  const lines = [];
1230
1272
  lines.push(`# Test brief \u2014 ${componentId}`);
1231
1273
  lines.push("");
@@ -1281,12 +1323,12 @@ function registerTestBriefTools(server, client) {
1281
1323
  content: [{ type: "text", text: markdown }],
1282
1324
  structuredContent: { componentId, forcedVariantId: forcedId, goalName, markdown }
1283
1325
  };
1284
- }
1326
+ })
1285
1327
  );
1286
1328
  }
1287
1329
 
1288
1330
  // src/tools/integration-guide.ts
1289
- import { z as z11 } from "zod";
1331
+ import { z as z12 } from "zod";
1290
1332
  var GUIDE = `# SentientUI integration guide \u2014 the adaptive ladder
1291
1333
 
1292
1334
  SentientUI adapts a site per visitor type (personas: buyer, researcher, deal_seeker, browser,
@@ -1361,7 +1403,7 @@ function registerIntegrationGuideTools(server) {
1361
1403
  description: "Get the SentientUI adaptive-ladder integration guide: setup (keyless and keyed) plus copy-pasteable examples for every rung (Style, Swap, Reorder). Use this to integrate SentientUI into a codebase.",
1362
1404
  inputSchema: {},
1363
1405
  outputSchema: {
1364
- guide: z11.string().describe("The full integration guide in Markdown")
1406
+ guide: z12.string().describe("The full integration guide in Markdown")
1365
1407
  },
1366
1408
  annotations: {
1367
1409
  readOnlyHint: true,
@@ -1377,7 +1419,8 @@ function registerIntegrationGuideTools(server) {
1377
1419
  }
1378
1420
 
1379
1421
  // src/server.ts
1380
- var { version: PKG_VERSION } = createRequire(import.meta.url)("../package.json");
1422
+ var import_meta = {};
1423
+ var { version: PKG_VERSION } = createRequire(import_meta.url)("../package.json");
1381
1424
  function createMcpServer(client) {
1382
1425
  const server = new McpServer(
1383
1426
  {