@sentientui/mcp 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-E34O3H2Z.js → chunk-GLL5CMBY.js} +268 -243
- package/dist/index.cjs +274 -251
- package/dist/index.js +7 -5
- package/dist/lib.cjs +269 -248
- package/dist/lib.js +1 -1
- package/package.json +10 -9
|
@@ -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
|
|
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
|
|
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,35 +40,64 @@ 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
|
|
43
|
+
throw new ApiError(res.status, String((_a = errBody.error) != null ? _a : res.statusText));
|
|
44
44
|
}
|
|
45
45
|
return res.json();
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
// src/server.ts
|
|
50
|
-
import { createRequire } from "module";
|
|
51
50
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
52
51
|
|
|
53
52
|
// src/tools/projects.ts
|
|
53
|
+
import { z as z2 } from "zod";
|
|
54
|
+
|
|
55
|
+
// src/tools/common.ts
|
|
54
56
|
import { z } from "zod";
|
|
55
57
|
var projectIdSchema = z.string().uuid().describe("The project UUID");
|
|
56
|
-
function
|
|
58
|
+
function apiErrorGuidance(err, extra) {
|
|
59
|
+
if (extra && Object.prototype.hasOwnProperty.call(extra, err.message)) {
|
|
60
|
+
return extra[err.message];
|
|
61
|
+
}
|
|
57
62
|
switch (err.message) {
|
|
58
63
|
case "insufficient_scope":
|
|
59
|
-
return "
|
|
64
|
+
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.";
|
|
60
65
|
case "demo_read_only":
|
|
61
|
-
return "Demo mode is read-only. Create a SentientUI account and sign in to make
|
|
66
|
+
return "Demo mode is read-only. Create a SentientUI account and sign in (or use a project server key) to make changes.";
|
|
62
67
|
case "insufficient_role":
|
|
63
|
-
return "Your account role
|
|
64
|
-
case "project_limit_reached":
|
|
65
|
-
return "You've reached your plan's project limit. Upgrade your plan or remove an existing project, then try again.";
|
|
66
|
-
case "name_required":
|
|
67
|
-
return "A project name is required to create a project.";
|
|
68
|
+
return "Your account role does not permit this action \u2014 it needs the account owner or an admin.";
|
|
68
69
|
default:
|
|
69
|
-
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
if (err.status === 402) {
|
|
73
|
+
return `This feature requires a higher plan (${err.message}). Upgrade your SentientUI plan, then try again.`;
|
|
70
74
|
}
|
|
75
|
+
if (err.status === 403) {
|
|
76
|
+
return `Access denied (${err.message}). Check that your key or login has access to this project.`;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
71
79
|
}
|
|
80
|
+
function withApiErrorGuidance(fn, extra) {
|
|
81
|
+
return async (args) => {
|
|
82
|
+
try {
|
|
83
|
+
return await fn(args);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (err instanceof ApiError) {
|
|
86
|
+
const guidance = apiErrorGuidance(err, extra);
|
|
87
|
+
if (guidance) {
|
|
88
|
+
return { content: [{ type: "text", text: guidance }], isError: true };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/tools/projects.ts
|
|
97
|
+
var CREATE_PROJECT_GUIDANCE = {
|
|
98
|
+
project_limit_reached: "You've reached your plan's project limit. Upgrade your plan or remove an existing project, then try again.",
|
|
99
|
+
name_required: "A project name is required to create a project."
|
|
100
|
+
};
|
|
72
101
|
function registerProjectTools(server, client) {
|
|
73
102
|
server.registerTool(
|
|
74
103
|
"create_project",
|
|
@@ -76,16 +105,16 @@ function registerProjectTools(server, client) {
|
|
|
76
105
|
title: "Create project",
|
|
77
106
|
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
107
|
inputSchema: {
|
|
79
|
-
name:
|
|
80
|
-
contextType:
|
|
81
|
-
framework:
|
|
82
|
-
websiteUrl:
|
|
108
|
+
name: z2.string().min(1).describe("Human-readable project name"),
|
|
109
|
+
contextType: z2.enum(["saas", "ecommerce", "marketing", "landing", "internal"]).optional().describe("What kind of product this is; defaults to saas"),
|
|
110
|
+
framework: z2.enum(["next", "react", "core"]).optional().describe("How the site is built \u2014 next, react, or core (website builder/CMS); defaults to next"),
|
|
111
|
+
websiteUrl: z2.string().optional().describe("Production site origin to allow-list so the SDK's events aren't origin-blocked on day one")
|
|
83
112
|
},
|
|
84
113
|
outputSchema: {
|
|
85
|
-
projectId:
|
|
86
|
-
publicKey:
|
|
87
|
-
name:
|
|
88
|
-
contextType:
|
|
114
|
+
projectId: z2.string().describe("The new project UUID"),
|
|
115
|
+
publicKey: z2.string().describe("The pk_ public key to configure the SDK with"),
|
|
116
|
+
name: z2.string().describe("The project name"),
|
|
117
|
+
contextType: z2.string().describe("The resolved context type")
|
|
89
118
|
},
|
|
90
119
|
annotations: {
|
|
91
120
|
readOnlyHint: false,
|
|
@@ -94,41 +123,31 @@ function registerProjectTools(server, client) {
|
|
|
94
123
|
openWorldHint: false
|
|
95
124
|
}
|
|
96
125
|
},
|
|
97
|
-
async ({ name, contextType, framework, websiteUrl }) => {
|
|
98
|
-
|
|
99
|
-
|
|
126
|
+
withApiErrorGuidance(async ({ name, contextType, framework, websiteUrl }) => {
|
|
127
|
+
const created = await client.post("/projects", {
|
|
128
|
+
name,
|
|
129
|
+
contextType,
|
|
130
|
+
framework,
|
|
131
|
+
origin: websiteUrl
|
|
132
|
+
});
|
|
133
|
+
const resolvedContextType = contextType != null ? contextType : "saas";
|
|
134
|
+
return {
|
|
135
|
+
content: [{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: [
|
|
138
|
+
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
139
|
+
`Public key: ${created.apiKey}`,
|
|
140
|
+
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
141
|
+
].join("\n")
|
|
142
|
+
}],
|
|
143
|
+
structuredContent: {
|
|
144
|
+
projectId: created.id,
|
|
145
|
+
publicKey: created.apiKey,
|
|
100
146
|
name,
|
|
101
|
-
contextType
|
|
102
|
-
framework,
|
|
103
|
-
origin: websiteUrl
|
|
104
|
-
});
|
|
105
|
-
const resolvedContextType = contextType ?? "saas";
|
|
106
|
-
return {
|
|
107
|
-
content: [{
|
|
108
|
-
type: "text",
|
|
109
|
-
text: [
|
|
110
|
-
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
111
|
-
`Public key: ${created.apiKey}`,
|
|
112
|
-
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
113
|
-
].join("\n")
|
|
114
|
-
}],
|
|
115
|
-
structuredContent: {
|
|
116
|
-
projectId: created.id,
|
|
117
|
-
publicKey: created.apiKey,
|
|
118
|
-
name,
|
|
119
|
-
contextType: resolvedContextType
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
} catch (err) {
|
|
123
|
-
if (err instanceof ApiError) {
|
|
124
|
-
const guidance = createProjectGuidance(err);
|
|
125
|
-
if (guidance) {
|
|
126
|
-
return { content: [{ type: "text", text: guidance }], isError: true };
|
|
127
|
-
}
|
|
147
|
+
contextType: resolvedContextType
|
|
128
148
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
149
|
+
};
|
|
150
|
+
}, CREATE_PROJECT_GUIDANCE)
|
|
132
151
|
);
|
|
133
152
|
server.registerTool(
|
|
134
153
|
"list_projects",
|
|
@@ -137,12 +156,12 @@ function registerProjectTools(server, client) {
|
|
|
137
156
|
description: "List all SentientUI projects for the authenticated account.",
|
|
138
157
|
inputSchema: {},
|
|
139
158
|
outputSchema: {
|
|
140
|
-
projects:
|
|
141
|
-
|
|
142
|
-
id:
|
|
143
|
-
name:
|
|
144
|
-
contextType:
|
|
145
|
-
createdAt:
|
|
159
|
+
projects: z2.array(
|
|
160
|
+
z2.object({
|
|
161
|
+
id: z2.string().describe("Project UUID"),
|
|
162
|
+
name: z2.string(),
|
|
163
|
+
contextType: z2.string(),
|
|
164
|
+
createdAt: z2.string().describe("ISO date (YYYY-MM-DD)")
|
|
146
165
|
})
|
|
147
166
|
).describe("All projects for the account (empty if none)")
|
|
148
167
|
},
|
|
@@ -152,7 +171,7 @@ function registerProjectTools(server, client) {
|
|
|
152
171
|
openWorldHint: false
|
|
153
172
|
}
|
|
154
173
|
},
|
|
155
|
-
async () => {
|
|
174
|
+
withApiErrorGuidance(async () => {
|
|
156
175
|
const projects = await client.get("/projects");
|
|
157
176
|
const text = projects.length === 0 ? "No projects found." : projects.map(
|
|
158
177
|
(p) => `- ${p.name} (id: ${p.id}, type: ${p.context_type}, created: ${p.created_at.slice(0, 10)})`
|
|
@@ -168,7 +187,7 @@ function registerProjectTools(server, client) {
|
|
|
168
187
|
}))
|
|
169
188
|
}
|
|
170
189
|
};
|
|
171
|
-
}
|
|
190
|
+
})
|
|
172
191
|
);
|
|
173
192
|
server.registerTool(
|
|
174
193
|
"get_project_stats",
|
|
@@ -177,11 +196,11 @@ function registerProjectTools(server, client) {
|
|
|
177
196
|
description: "Get health stats for a project: event volume, session count, agent calls, and status.",
|
|
178
197
|
inputSchema: { projectId: projectIdSchema },
|
|
179
198
|
outputSchema: {
|
|
180
|
-
status:
|
|
181
|
-
events24h:
|
|
182
|
-
sessions24h:
|
|
183
|
-
agentCalls:
|
|
184
|
-
lastEventAt:
|
|
199
|
+
status: z2.string().describe("Overall project health status"),
|
|
200
|
+
events24h: z2.number().describe("Events in the last 24 hours"),
|
|
201
|
+
sessions24h: z2.number().describe("Sessions in the last 24 hours"),
|
|
202
|
+
agentCalls: z2.number().describe("Total agent (MCP/API) calls"),
|
|
203
|
+
lastEventAt: z2.string().nullable().describe("ISO timestamp of the last event, or null")
|
|
185
204
|
},
|
|
186
205
|
annotations: {
|
|
187
206
|
readOnlyHint: true,
|
|
@@ -189,7 +208,8 @@ function registerProjectTools(server, client) {
|
|
|
189
208
|
openWorldHint: false
|
|
190
209
|
}
|
|
191
210
|
},
|
|
192
|
-
async ({ projectId }) => {
|
|
211
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
212
|
+
var _a;
|
|
193
213
|
const id = encodeURIComponent(projectId);
|
|
194
214
|
const stats = await client.get(`/projects/${id}/health`);
|
|
195
215
|
const text = [
|
|
@@ -197,7 +217,7 @@ function registerProjectTools(server, client) {
|
|
|
197
217
|
`Events (24h): ${stats.events24h}`,
|
|
198
218
|
`Sessions (24h): ${stats.sessions24h}`,
|
|
199
219
|
`Agent calls (total): ${stats.agentCalls}`,
|
|
200
|
-
`Last event: ${stats.lastEventAt
|
|
220
|
+
`Last event: ${(_a = stats.lastEventAt) != null ? _a : "never"}`
|
|
201
221
|
].join("\n");
|
|
202
222
|
return {
|
|
203
223
|
content: [{ type: "text", text }],
|
|
@@ -209,12 +229,12 @@ function registerProjectTools(server, client) {
|
|
|
209
229
|
lastEventAt: stats.lastEventAt
|
|
210
230
|
}
|
|
211
231
|
};
|
|
212
|
-
}
|
|
232
|
+
})
|
|
213
233
|
);
|
|
214
234
|
}
|
|
215
235
|
|
|
216
236
|
// src/tools/components.ts
|
|
217
|
-
import { z as
|
|
237
|
+
import { z as z3 } from "zod";
|
|
218
238
|
|
|
219
239
|
// src/ui/templates.ts
|
|
220
240
|
var VIZ_TITLES = {
|
|
@@ -473,21 +493,20 @@ function registerUiResources(server) {
|
|
|
473
493
|
}
|
|
474
494
|
|
|
475
495
|
// src/tools/components.ts
|
|
476
|
-
var projectIdSchema2 = z2.string().uuid().describe("The project UUID");
|
|
477
496
|
function registerComponentTools(server, client) {
|
|
478
497
|
server.registerTool(
|
|
479
498
|
"list_components",
|
|
480
499
|
{
|
|
481
500
|
title: "List components",
|
|
482
501
|
description: "List all adaptive components in a project with variant counts and impression totals.",
|
|
483
|
-
inputSchema: { projectId:
|
|
502
|
+
inputSchema: { projectId: projectIdSchema },
|
|
484
503
|
outputSchema: {
|
|
485
|
-
components:
|
|
486
|
-
|
|
487
|
-
componentId:
|
|
488
|
-
variantCount:
|
|
489
|
-
impressions:
|
|
490
|
-
conversions:
|
|
504
|
+
components: z3.array(
|
|
505
|
+
z3.object({
|
|
506
|
+
componentId: z3.string(),
|
|
507
|
+
variantCount: z3.number(),
|
|
508
|
+
impressions: z3.number(),
|
|
509
|
+
conversions: z3.number()
|
|
491
510
|
})
|
|
492
511
|
).describe("Adaptive components in the project (empty if none)")
|
|
493
512
|
},
|
|
@@ -497,7 +516,7 @@ function registerComponentTools(server, client) {
|
|
|
497
516
|
openWorldHint: false
|
|
498
517
|
}
|
|
499
518
|
},
|
|
500
|
-
async ({ projectId }) => {
|
|
519
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
501
520
|
const id = encodeURIComponent(projectId);
|
|
502
521
|
const { components } = await client.get(`/projects/${id}/components`);
|
|
503
522
|
const structuredContent = {
|
|
@@ -518,23 +537,23 @@ function registerComponentTools(server, client) {
|
|
|
518
537
|
(c) => `- ${c.component_id}: ${c.variants.length} variants, ${c.total_impressions} impressions, ${c.total_conversions} conversions`
|
|
519
538
|
).join("\n");
|
|
520
539
|
return { content: [{ type: "text", text }], structuredContent };
|
|
521
|
-
}
|
|
540
|
+
})
|
|
522
541
|
);
|
|
523
542
|
server.registerTool(
|
|
524
543
|
"get_variant_performance",
|
|
525
544
|
{
|
|
526
545
|
title: "Variant performance",
|
|
527
546
|
description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
|
|
528
|
-
inputSchema: { projectId:
|
|
547
|
+
inputSchema: { projectId: projectIdSchema },
|
|
529
548
|
_meta: uiMeta("variant-performance"),
|
|
530
549
|
outputSchema: {
|
|
531
|
-
variants:
|
|
532
|
-
|
|
533
|
-
variantId:
|
|
534
|
-
currentCvr:
|
|
535
|
-
priorCvr:
|
|
536
|
-
deltaPp:
|
|
537
|
-
momentum:
|
|
550
|
+
variants: z3.array(
|
|
551
|
+
z3.object({
|
|
552
|
+
variantId: z3.string(),
|
|
553
|
+
currentCvr: z3.number().describe("Conversion rate over the last 7 days (0-1)"),
|
|
554
|
+
priorCvr: z3.number().describe("Conversion rate over the prior 7 days (0-1)"),
|
|
555
|
+
deltaPp: z3.number().describe("Change in percentage points"),
|
|
556
|
+
momentum: z3.string().describe("Momentum direction: gaining, losing, or stable")
|
|
538
557
|
})
|
|
539
558
|
).describe("Per-variant performance (empty if no data yet)")
|
|
540
559
|
},
|
|
@@ -544,20 +563,24 @@ function registerComponentTools(server, client) {
|
|
|
544
563
|
openWorldHint: false
|
|
545
564
|
}
|
|
546
565
|
},
|
|
547
|
-
async ({ projectId }) => {
|
|
566
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
567
|
+
var _a, _b, _c;
|
|
548
568
|
const id = encodeURIComponent(projectId);
|
|
549
569
|
const data = await client.get(`/projects/${id}/trends`);
|
|
550
|
-
const momentumMap = new Map((data.momentum
|
|
570
|
+
const momentumMap = new Map(((_a = data.momentum) != null ? _a : []).map((m) => [m.variantId, m.direction]));
|
|
551
571
|
const structuredContent = {
|
|
552
|
-
variants: (data.cvr
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
572
|
+
variants: ((_b = data.cvr) != null ? _b : []).map((v) => {
|
|
573
|
+
var _a2;
|
|
574
|
+
return {
|
|
575
|
+
variantId: v.variantId,
|
|
576
|
+
currentCvr: v.currentCvr,
|
|
577
|
+
priorCvr: v.priorCvr,
|
|
578
|
+
deltaPp: v.deltaPp,
|
|
579
|
+
momentum: (_a2 = momentumMap.get(v.variantId)) != null ? _a2 : "stable"
|
|
580
|
+
};
|
|
581
|
+
})
|
|
559
582
|
};
|
|
560
|
-
if (!data.cvr
|
|
583
|
+
if (!((_c = data.cvr) == null ? void 0 : _c.length)) {
|
|
561
584
|
return {
|
|
562
585
|
content: [{ type: "text", text: "No variant data available yet." }],
|
|
563
586
|
structuredContent,
|
|
@@ -565,29 +588,31 @@ function registerComponentTools(server, client) {
|
|
|
565
588
|
};
|
|
566
589
|
}
|
|
567
590
|
const text = data.cvr.map(
|
|
568
|
-
(v) =>
|
|
591
|
+
(v) => {
|
|
592
|
+
var _a2;
|
|
593
|
+
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"})`;
|
|
594
|
+
}
|
|
569
595
|
).join("\n");
|
|
570
596
|
return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
|
|
571
|
-
}
|
|
597
|
+
})
|
|
572
598
|
);
|
|
573
599
|
}
|
|
574
600
|
|
|
575
601
|
// src/tools/insights.ts
|
|
576
|
-
import { z as
|
|
577
|
-
var projectIdSchema3 = z3.string().uuid().describe("The project UUID");
|
|
602
|
+
import { z as z4 } from "zod";
|
|
578
603
|
function registerInsightTools(server, client) {
|
|
579
604
|
server.registerTool(
|
|
580
605
|
"get_insights",
|
|
581
606
|
{
|
|
582
607
|
title: "Get insights",
|
|
583
608
|
description: "Get the latest AI-generated insights: narrator observations and (Growth tier) advisor recommendations.",
|
|
584
|
-
inputSchema: { projectId:
|
|
609
|
+
inputSchema: { projectId: projectIdSchema },
|
|
585
610
|
outputSchema: {
|
|
586
|
-
status:
|
|
587
|
-
observations:
|
|
588
|
-
recommendations:
|
|
589
|
-
isStale:
|
|
590
|
-
generatedAt:
|
|
611
|
+
status: z4.enum(["ok", "empty"]).describe("Whether insights exist yet"),
|
|
612
|
+
observations: z4.array(z4.string()).describe("Narrator observations"),
|
|
613
|
+
recommendations: z4.array(z4.string()).describe("Advisor recommendations (Growth tier)"),
|
|
614
|
+
isStale: z4.boolean().describe("True when the insights are older than ~6h"),
|
|
615
|
+
generatedAt: z4.string().nullable().describe("ISO timestamp the insights were generated, or null")
|
|
591
616
|
},
|
|
592
617
|
annotations: {
|
|
593
618
|
readOnlyHint: true,
|
|
@@ -595,7 +620,8 @@ function registerInsightTools(server, client) {
|
|
|
595
620
|
openWorldHint: false
|
|
596
621
|
}
|
|
597
622
|
},
|
|
598
|
-
async ({ projectId }) => {
|
|
623
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
624
|
+
var _a, _b, _c, _d;
|
|
599
625
|
const id = encodeURIComponent(projectId);
|
|
600
626
|
const data = await client.get(`/projects/${id}/insights`);
|
|
601
627
|
if (data.status === "empty") {
|
|
@@ -610,8 +636,8 @@ function registerInsightTools(server, client) {
|
|
|
610
636
|
}
|
|
611
637
|
};
|
|
612
638
|
}
|
|
613
|
-
const observations = data.narratorBullets
|
|
614
|
-
const recommendations = data.advisorBullets
|
|
639
|
+
const observations = (_a = data.narratorBullets) != null ? _a : [];
|
|
640
|
+
const recommendations = (_b = data.advisorBullets) != null ? _b : [];
|
|
615
641
|
const lines = [];
|
|
616
642
|
if (data.isStale) lines.push("\u26A0 Insights are stale (>6h old). Consider calling refresh_insights.");
|
|
617
643
|
if (data.generatedAt) lines.push(`Generated: ${new Date(data.generatedAt).toUTCString()}`);
|
|
@@ -629,33 +655,32 @@ function registerInsightTools(server, client) {
|
|
|
629
655
|
status: "ok",
|
|
630
656
|
observations,
|
|
631
657
|
recommendations,
|
|
632
|
-
isStale: data.isStale
|
|
633
|
-
generatedAt: data.generatedAt
|
|
658
|
+
isStale: (_c = data.isStale) != null ? _c : false,
|
|
659
|
+
generatedAt: (_d = data.generatedAt) != null ? _d : null
|
|
634
660
|
}
|
|
635
661
|
};
|
|
636
|
-
}
|
|
662
|
+
})
|
|
637
663
|
);
|
|
638
664
|
}
|
|
639
665
|
|
|
640
666
|
// src/tools/personas.ts
|
|
641
|
-
import { z as
|
|
642
|
-
var projectIdSchema4 = z4.string().uuid().describe("The project UUID");
|
|
667
|
+
import { z as z5 } from "zod";
|
|
643
668
|
function registerPersonaTools(server, client) {
|
|
644
669
|
server.registerTool(
|
|
645
670
|
"get_persona_breakdown",
|
|
646
671
|
{
|
|
647
672
|
title: "Persona breakdown",
|
|
648
673
|
description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
|
|
649
|
-
inputSchema: { projectId:
|
|
674
|
+
inputSchema: { projectId: projectIdSchema },
|
|
650
675
|
_meta: uiMeta("persona-breakdown"),
|
|
651
676
|
outputSchema: {
|
|
652
|
-
totalSessions:
|
|
653
|
-
clusters:
|
|
654
|
-
|
|
655
|
-
label:
|
|
656
|
-
sessionCount:
|
|
657
|
-
sharePct:
|
|
658
|
-
reliability:
|
|
677
|
+
totalSessions: z5.number().describe("Total sessions across all clusters"),
|
|
678
|
+
clusters: z5.array(
|
|
679
|
+
z5.object({
|
|
680
|
+
label: z5.string(),
|
|
681
|
+
sessionCount: z5.number(),
|
|
682
|
+
sharePct: z5.number().describe("Share of total traffic (0-100)"),
|
|
683
|
+
reliability: z5.number().describe("Average cluster reliability (0-1)")
|
|
659
684
|
})
|
|
660
685
|
).describe("Persona clusters (empty until enough visitor data)")
|
|
661
686
|
},
|
|
@@ -665,7 +690,7 @@ function registerPersonaTools(server, client) {
|
|
|
665
690
|
openWorldHint: false
|
|
666
691
|
}
|
|
667
692
|
},
|
|
668
|
-
async ({ projectId }) => {
|
|
693
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
669
694
|
const id = encodeURIComponent(projectId);
|
|
670
695
|
const data = await client.get(`/projects/${id}/portraits`);
|
|
671
696
|
const structuredContent = {
|
|
@@ -694,33 +719,32 @@ function registerPersonaTools(server, client) {
|
|
|
694
719
|
})
|
|
695
720
|
];
|
|
696
721
|
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent, _meta: uiMeta("persona-breakdown") };
|
|
697
|
-
}
|
|
722
|
+
})
|
|
698
723
|
);
|
|
699
724
|
}
|
|
700
725
|
|
|
701
726
|
// src/tools/goals.ts
|
|
702
|
-
import { z as
|
|
703
|
-
var projectIdSchema5 = z5.string().uuid().describe("The project UUID");
|
|
727
|
+
import { z as z6 } from "zod";
|
|
704
728
|
function registerGoalTools(server, client) {
|
|
705
729
|
server.registerTool(
|
|
706
730
|
"get_goal_funnel",
|
|
707
731
|
{
|
|
708
732
|
title: "Goal funnel",
|
|
709
733
|
description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
|
|
710
|
-
inputSchema: { projectId:
|
|
734
|
+
inputSchema: { projectId: projectIdSchema },
|
|
711
735
|
_meta: uiMeta("goal-funnel"),
|
|
712
736
|
outputSchema: {
|
|
713
|
-
goals:
|
|
714
|
-
|
|
715
|
-
goalName:
|
|
716
|
-
hits:
|
|
717
|
-
uniqueSessions:
|
|
718
|
-
conversionRate:
|
|
719
|
-
variants:
|
|
720
|
-
|
|
721
|
-
componentId:
|
|
722
|
-
variantId:
|
|
723
|
-
completionRate:
|
|
737
|
+
goals: z6.array(
|
|
738
|
+
z6.object({
|
|
739
|
+
goalName: z6.string(),
|
|
740
|
+
hits: z6.number(),
|
|
741
|
+
uniqueSessions: z6.number(),
|
|
742
|
+
conversionRate: z6.number().describe("Unique-session conversion rate (0-1)"),
|
|
743
|
+
variants: z6.array(
|
|
744
|
+
z6.object({
|
|
745
|
+
componentId: z6.string(),
|
|
746
|
+
variantId: z6.string(),
|
|
747
|
+
completionRate: z6.number().describe("Completion rate per assigned session (0-1)")
|
|
724
748
|
})
|
|
725
749
|
).describe("Per-variant breakdown")
|
|
726
750
|
})
|
|
@@ -732,7 +756,7 @@ function registerGoalTools(server, client) {
|
|
|
732
756
|
openWorldHint: false
|
|
733
757
|
}
|
|
734
758
|
},
|
|
735
|
-
async ({ projectId }) => {
|
|
759
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
736
760
|
const id = encodeURIComponent(projectId);
|
|
737
761
|
const data = await client.get(`/projects/${id}/goals`);
|
|
738
762
|
const structuredContent = {
|
|
@@ -761,26 +785,25 @@ function registerGoalTools(server, client) {
|
|
|
761
785
|
""
|
|
762
786
|
]);
|
|
763
787
|
return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
|
|
764
|
-
}
|
|
788
|
+
})
|
|
765
789
|
);
|
|
766
790
|
}
|
|
767
791
|
|
|
768
792
|
// src/tools/guardrails.ts
|
|
769
|
-
import { z as
|
|
770
|
-
var projectIdSchema6 = z6.string().uuid().describe("The project UUID");
|
|
793
|
+
import { z as z7 } from "zod";
|
|
771
794
|
function registerGuardrailTools(server, client) {
|
|
772
795
|
server.registerTool(
|
|
773
796
|
"list_guardrail_events",
|
|
774
797
|
{
|
|
775
798
|
title: "List guardrail events",
|
|
776
799
|
description: "List variants currently paused by the guardrail in the last 24 hours.",
|
|
777
|
-
inputSchema: { projectId:
|
|
800
|
+
inputSchema: { projectId: projectIdSchema },
|
|
778
801
|
outputSchema: {
|
|
779
|
-
events:
|
|
780
|
-
|
|
781
|
-
componentId:
|
|
782
|
-
variantIds:
|
|
783
|
-
pausedAt:
|
|
802
|
+
events: z7.array(
|
|
803
|
+
z7.object({
|
|
804
|
+
componentId: z7.string(),
|
|
805
|
+
variantIds: z7.array(z7.string()).describe("Variants paused by the guardrail"),
|
|
806
|
+
pausedAt: z7.string().nullable().describe("ISO timestamp the pause fired, or null")
|
|
784
807
|
})
|
|
785
808
|
).describe("Guardrail events in the last 24h (empty if none)")
|
|
786
809
|
},
|
|
@@ -790,7 +813,7 @@ function registerGuardrailTools(server, client) {
|
|
|
790
813
|
openWorldHint: false
|
|
791
814
|
}
|
|
792
815
|
},
|
|
793
|
-
async ({ projectId }) => {
|
|
816
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
794
817
|
const id = encodeURIComponent(projectId);
|
|
795
818
|
const data = await client.get(`/projects/${id}/guardrail-events`);
|
|
796
819
|
const structuredContent = {
|
|
@@ -810,28 +833,27 @@ function registerGuardrailTools(server, client) {
|
|
|
810
833
|
(e) => `- ${e.componentId}: variants [${e.variantIds.join(", ")}] paused${e.pausedAt ? ` at ${e.pausedAt}` : ""}`
|
|
811
834
|
);
|
|
812
835
|
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
813
|
-
}
|
|
836
|
+
})
|
|
814
837
|
);
|
|
815
838
|
}
|
|
816
839
|
|
|
817
840
|
// src/tools/layout.ts
|
|
818
|
-
import { z as
|
|
819
|
-
var projectIdSchema7 = z7.string().uuid().describe("The project UUID");
|
|
841
|
+
import { z as z8 } from "zod";
|
|
820
842
|
function registerLayoutTools(server, client) {
|
|
821
843
|
server.registerTool(
|
|
822
844
|
"get_layout_stats",
|
|
823
845
|
{
|
|
824
846
|
title: "Layout stats",
|
|
825
847
|
description: "Get per-persona section layout rankings and bandit reward weights.",
|
|
826
|
-
inputSchema: { projectId:
|
|
848
|
+
inputSchema: { projectId: projectIdSchema },
|
|
827
849
|
_meta: uiMeta("layout-stats"),
|
|
828
850
|
outputSchema: {
|
|
829
|
-
layouts:
|
|
830
|
-
|
|
831
|
-
persona:
|
|
832
|
-
layoutOrder:
|
|
833
|
-
pulls:
|
|
834
|
-
avgReward:
|
|
851
|
+
layouts: z8.array(
|
|
852
|
+
z8.object({
|
|
853
|
+
persona: z8.string(),
|
|
854
|
+
layoutOrder: z8.array(z8.string()).describe("Ranked section order for this persona"),
|
|
855
|
+
pulls: z8.number().describe("Number of times this arm was served"),
|
|
856
|
+
avgReward: z8.number().describe("Average bandit reward weight")
|
|
835
857
|
})
|
|
836
858
|
).describe("Per-persona layout rankings (empty until enough sessions)")
|
|
837
859
|
},
|
|
@@ -841,7 +863,7 @@ function registerLayoutTools(server, client) {
|
|
|
841
863
|
openWorldHint: false
|
|
842
864
|
}
|
|
843
865
|
},
|
|
844
|
-
async ({ projectId }) => {
|
|
866
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
845
867
|
const id = encodeURIComponent(projectId);
|
|
846
868
|
const stats = await client.get(`/projects/${id}/layout-stats`);
|
|
847
869
|
const structuredContent = {
|
|
@@ -863,13 +885,12 @@ function registerLayoutTools(server, client) {
|
|
|
863
885
|
(s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
|
|
864
886
|
).join("\n");
|
|
865
887
|
return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("layout-stats") };
|
|
866
|
-
}
|
|
888
|
+
})
|
|
867
889
|
);
|
|
868
890
|
}
|
|
869
891
|
|
|
870
892
|
// src/tools/variants.ts
|
|
871
|
-
import { z as
|
|
872
|
-
var projectIdSchema8 = z8.string().uuid().describe("The project UUID");
|
|
893
|
+
import { z as z9 } from "zod";
|
|
873
894
|
function registerVariantWriteTools(server, client) {
|
|
874
895
|
server.registerTool(
|
|
875
896
|
"create_variant",
|
|
@@ -877,17 +898,20 @@ function registerVariantWriteTools(server, client) {
|
|
|
877
898
|
title: "Create managed variant",
|
|
878
899
|
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
900
|
inputSchema: {
|
|
880
|
-
projectId:
|
|
881
|
-
componentId:
|
|
882
|
-
displayName:
|
|
883
|
-
content:
|
|
901
|
+
projectId: projectIdSchema,
|
|
902
|
+
componentId: z9.string().min(1).max(200).describe("The component ID to add a variant to"),
|
|
903
|
+
displayName: z9.string().min(1).max(200).describe("Human-readable name for the new variant"),
|
|
904
|
+
content: z9.string().max(1e4).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
905
|
},
|
|
885
906
|
outputSchema: {
|
|
886
|
-
variantId:
|
|
887
|
-
displayName
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
907
|
+
variantId: z9.string().describe("The new variant ID"),
|
|
908
|
+
// API returns `body.displayName ?? null`, so a successful create can
|
|
909
|
+
// carry a null name — match that contract or outputSchema validation
|
|
910
|
+
// would reject an otherwise-successful response.
|
|
911
|
+
displayName: z9.string().nullable(),
|
|
912
|
+
componentId: z9.string(),
|
|
913
|
+
state: z9.literal("draft").describe("New managed variants start in draft state"),
|
|
914
|
+
hasContent: z9.boolean().describe("Whether text content was provided at creation")
|
|
891
915
|
},
|
|
892
916
|
annotations: {
|
|
893
917
|
readOnlyHint: false,
|
|
@@ -896,7 +920,7 @@ function registerVariantWriteTools(server, client) {
|
|
|
896
920
|
openWorldHint: false
|
|
897
921
|
}
|
|
898
922
|
},
|
|
899
|
-
async ({ projectId, componentId, displayName, content }) => {
|
|
923
|
+
withApiErrorGuidance(async ({ projectId, componentId, displayName, content }) => {
|
|
900
924
|
const id = encodeURIComponent(projectId);
|
|
901
925
|
const result = await client.post(
|
|
902
926
|
`/projects/${id}/variants`,
|
|
@@ -916,7 +940,7 @@ function registerVariantWriteTools(server, client) {
|
|
|
916
940
|
hasContent: Boolean(content)
|
|
917
941
|
}
|
|
918
942
|
};
|
|
919
|
-
}
|
|
943
|
+
})
|
|
920
944
|
);
|
|
921
945
|
server.registerTool(
|
|
922
946
|
"pause_variant",
|
|
@@ -924,14 +948,14 @@ function registerVariantWriteTools(server, client) {
|
|
|
924
948
|
title: "Pause variant",
|
|
925
949
|
description: "Pause a variant, stopping traffic from being assigned to it.",
|
|
926
950
|
inputSchema: {
|
|
927
|
-
projectId:
|
|
928
|
-
componentId:
|
|
929
|
-
variantId:
|
|
951
|
+
projectId: projectIdSchema,
|
|
952
|
+
componentId: z9.string().describe("The component ID"),
|
|
953
|
+
variantId: z9.string().describe("The variant ID to pause")
|
|
930
954
|
},
|
|
931
955
|
outputSchema: {
|
|
932
|
-
variantId:
|
|
933
|
-
componentId:
|
|
934
|
-
paused:
|
|
956
|
+
variantId: z9.string(),
|
|
957
|
+
componentId: z9.string(),
|
|
958
|
+
paused: z9.literal(true).describe("The variant is now paused")
|
|
935
959
|
},
|
|
936
960
|
annotations: {
|
|
937
961
|
readOnlyHint: false,
|
|
@@ -940,7 +964,7 @@ function registerVariantWriteTools(server, client) {
|
|
|
940
964
|
openWorldHint: false
|
|
941
965
|
}
|
|
942
966
|
},
|
|
943
|
-
async ({ projectId, componentId, variantId }) => {
|
|
967
|
+
withApiErrorGuidance(async ({ projectId, componentId, variantId }) => {
|
|
944
968
|
const id = encodeURIComponent(projectId);
|
|
945
969
|
await client.post(`/projects/${id}/variants/pause`, { componentId, variantId });
|
|
946
970
|
return {
|
|
@@ -950,17 +974,17 @@ function registerVariantWriteTools(server, client) {
|
|
|
950
974
|
}],
|
|
951
975
|
structuredContent: { variantId, componentId, paused: true }
|
|
952
976
|
};
|
|
953
|
-
}
|
|
977
|
+
})
|
|
954
978
|
);
|
|
955
979
|
server.registerTool(
|
|
956
980
|
"refresh_insights",
|
|
957
981
|
{
|
|
958
982
|
title: "Refresh insights",
|
|
959
983
|
description: "Trigger fresh AI insight generation for a project. Returns immediately; use get_insights in ~15 seconds to see results.",
|
|
960
|
-
inputSchema: { projectId:
|
|
984
|
+
inputSchema: { projectId: projectIdSchema },
|
|
961
985
|
outputSchema: {
|
|
962
|
-
projectId:
|
|
963
|
-
status:
|
|
986
|
+
projectId: z9.string(),
|
|
987
|
+
status: z9.literal("generating").describe("Generation has been triggered")
|
|
964
988
|
},
|
|
965
989
|
annotations: {
|
|
966
990
|
readOnlyHint: false,
|
|
@@ -969,7 +993,7 @@ function registerVariantWriteTools(server, client) {
|
|
|
969
993
|
openWorldHint: false
|
|
970
994
|
}
|
|
971
995
|
},
|
|
972
|
-
async ({ projectId }) => {
|
|
996
|
+
withApiErrorGuidance(async ({ projectId }) => {
|
|
973
997
|
const id = encodeURIComponent(projectId);
|
|
974
998
|
await client.post(`/projects/${id}/insights/refresh`);
|
|
975
999
|
return {
|
|
@@ -979,13 +1003,12 @@ function registerVariantWriteTools(server, client) {
|
|
|
979
1003
|
}],
|
|
980
1004
|
structuredContent: { projectId, status: "generating" }
|
|
981
1005
|
};
|
|
982
|
-
}
|
|
1006
|
+
})
|
|
983
1007
|
);
|
|
984
1008
|
}
|
|
985
1009
|
|
|
986
1010
|
// src/tools/variant-brief.ts
|
|
987
|
-
import { z as
|
|
988
|
-
var projectIdSchema9 = z9.string().uuid().describe("The project UUID");
|
|
1011
|
+
import { z as z10 } from "zod";
|
|
989
1012
|
var GOAL_TARGET = 500;
|
|
990
1013
|
var BEST_PRACTICE_PRIORS = {
|
|
991
1014
|
ecommerce: [
|
|
@@ -1023,11 +1046,12 @@ var GENERIC_PRIORS = [
|
|
|
1023
1046
|
"Add one credible proof point near the action."
|
|
1024
1047
|
];
|
|
1025
1048
|
function priorsFor(contextType) {
|
|
1026
|
-
|
|
1049
|
+
var _a;
|
|
1050
|
+
return (_a = BEST_PRACTICE_PRIORS[contextType]) != null ? _a : GENERIC_PRIORS;
|
|
1027
1051
|
}
|
|
1028
1052
|
function computeDataState(impressions, insights, avgReliability) {
|
|
1029
1053
|
if (impressions === 0) return "empty";
|
|
1030
|
-
const insightsReady = insights
|
|
1054
|
+
const insightsReady = (insights == null ? void 0 : insights.status) === "ok" && !insights.isStale;
|
|
1031
1055
|
const reliable = avgReliability === null || avgReliability >= 0.3;
|
|
1032
1056
|
if (impressions < GOAL_TARGET || !insightsReady || !reliable) return "collecting";
|
|
1033
1057
|
return "sufficient";
|
|
@@ -1045,7 +1069,7 @@ function guidanceFor(dataState, contextType) {
|
|
|
1045
1069
|
async function settled(p) {
|
|
1046
1070
|
try {
|
|
1047
1071
|
return await p;
|
|
1048
|
-
} catch {
|
|
1072
|
+
} catch (e) {
|
|
1049
1073
|
return null;
|
|
1050
1074
|
}
|
|
1051
1075
|
}
|
|
@@ -1056,16 +1080,16 @@ function registerVariantBriefTools(server, client) {
|
|
|
1056
1080
|
title: "Variant brief",
|
|
1057
1081
|
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
1082
|
inputSchema: {
|
|
1059
|
-
projectId:
|
|
1060
|
-
componentId:
|
|
1083
|
+
projectId: projectIdSchema,
|
|
1084
|
+
componentId: z10.string().describe('The component ID to write a new variant for (matches <Adaptive id="...">).')
|
|
1061
1085
|
},
|
|
1062
1086
|
outputSchema: {
|
|
1063
|
-
componentId:
|
|
1064
|
-
contextType:
|
|
1065
|
-
dataState:
|
|
1066
|
-
existingVariantIds:
|
|
1067
|
-
priors:
|
|
1068
|
-
markdown:
|
|
1087
|
+
componentId: z10.string(),
|
|
1088
|
+
contextType: z10.string().describe("The project's context type (or 'unknown')"),
|
|
1089
|
+
dataState: z10.enum(["sufficient", "collecting", "empty"]).describe("Data-sufficiency assessment"),
|
|
1090
|
+
existingVariantIds: z10.array(z10.string()).describe("Variant IDs already in use (do not reuse)"),
|
|
1091
|
+
priors: z10.array(z10.string()).describe("Best-practice priors applied for this context type"),
|
|
1092
|
+
markdown: z10.string().describe("The full variant brief in Markdown")
|
|
1069
1093
|
},
|
|
1070
1094
|
annotations: {
|
|
1071
1095
|
readOnlyHint: true,
|
|
@@ -1073,7 +1097,8 @@ function registerVariantBriefTools(server, client) {
|
|
|
1073
1097
|
openWorldHint: false
|
|
1074
1098
|
}
|
|
1075
1099
|
},
|
|
1076
|
-
async ({ projectId, componentId }) => {
|
|
1100
|
+
withApiErrorGuidance(async ({ projectId, componentId }) => {
|
|
1101
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
1077
1102
|
const id = encodeURIComponent(projectId);
|
|
1078
1103
|
const [projects, componentsEnvelope, trends, portraits, insights] = await Promise.all([
|
|
1079
1104
|
settled(client.get("/projects")),
|
|
@@ -1083,19 +1108,19 @@ function registerVariantBriefTools(server, client) {
|
|
|
1083
1108
|
settled(client.get(`/projects/${id}/portraits`)),
|
|
1084
1109
|
settled(client.get(`/projects/${id}/insights`))
|
|
1085
1110
|
]);
|
|
1086
|
-
const project = projects
|
|
1087
|
-
const contextType = project
|
|
1088
|
-
const components = componentsEnvelope
|
|
1089
|
-
const component = components.find((c) => c.component_id === componentId)
|
|
1090
|
-
const impressions = component
|
|
1091
|
-
const conversions = component
|
|
1111
|
+
const project = (_a = projects == null ? void 0 : projects.find((p) => p.id === projectId)) != null ? _a : null;
|
|
1112
|
+
const contextType = (_b = project == null ? void 0 : project.context_type) != null ? _b : "unknown";
|
|
1113
|
+
const components = (_c = componentsEnvelope == null ? void 0 : componentsEnvelope.components) != null ? _c : [];
|
|
1114
|
+
const component = (_d = components.find((c) => c.component_id === componentId)) != null ? _d : null;
|
|
1115
|
+
const impressions = (_e = component == null ? void 0 : component.total_impressions) != null ? _e : 0;
|
|
1116
|
+
const conversions = (_f = component == null ? void 0 : component.total_conversions) != null ? _f : 0;
|
|
1092
1117
|
const componentCvr = impressions > 0 ? conversions / impressions * 100 : 0;
|
|
1093
|
-
const existingVariantIds = component
|
|
1118
|
+
const existingVariantIds = (_g = component == null ? void 0 : component.variants.map((v) => v.variant_id)) != null ? _g : [];
|
|
1094
1119
|
const variantIdSet = new Set(existingVariantIds);
|
|
1095
|
-
const momentumMap = new Map((trends
|
|
1096
|
-
const variantPerf = (trends
|
|
1097
|
-
const clusters = portraits
|
|
1098
|
-
const totalSessions = portraits
|
|
1120
|
+
const momentumMap = new Map(((_h = trends == null ? void 0 : trends.momentum) != null ? _h : []).map((m) => [m.variantId, m.direction]));
|
|
1121
|
+
const variantPerf = ((_i = trends == null ? void 0 : trends.cvr) != null ? _i : []).filter((v) => variantIdSet.has(v.variantId));
|
|
1122
|
+
const clusters = (_j = portraits == null ? void 0 : portraits.clusters) != null ? _j : [];
|
|
1123
|
+
const totalSessions = (_k = portraits == null ? void 0 : portraits.totalSessions) != null ? _k : 0;
|
|
1099
1124
|
const avgReliability = clusters.length ? clusters.reduce((s, c) => s + c.avgReliability, 0) / clusters.length : null;
|
|
1100
1125
|
const dataState = computeDataState(impressions, insights, avgReliability);
|
|
1101
1126
|
const lines = [];
|
|
@@ -1120,7 +1145,7 @@ function registerVariantBriefTools(server, client) {
|
|
|
1120
1145
|
lines.push("Current variant performance (7d vs prior 7d):");
|
|
1121
1146
|
for (const v of variantPerf) {
|
|
1122
1147
|
lines.push(
|
|
1123
|
-
`- ${v.variantId}: ${(v.currentCvr * 100).toFixed(2)}% CVR (${v.deltaPp > 0 ? "+" : ""}${v.deltaPp.toFixed(1)} pp, ${momentumMap.get(v.variantId)
|
|
1148
|
+
`- ${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
1149
|
);
|
|
1125
1150
|
}
|
|
1126
1151
|
lines.push("");
|
|
@@ -1135,8 +1160,8 @@ function registerVariantBriefTools(server, client) {
|
|
|
1135
1160
|
}
|
|
1136
1161
|
if (insights && insights.status === "ok") {
|
|
1137
1162
|
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
|
|
1163
|
+
const narrator = (_m = insights.narratorBullets) != null ? _m : [];
|
|
1164
|
+
const advisor = (_n = insights.advisorBullets) != null ? _n : [];
|
|
1140
1165
|
if (narrator.length) {
|
|
1141
1166
|
lines.push("Insights \u2014 observations:");
|
|
1142
1167
|
narrator.forEach((b) => lines.push(`- ${b}`));
|
|
@@ -1176,17 +1201,16 @@ function registerVariantBriefTools(server, client) {
|
|
|
1176
1201
|
markdown
|
|
1177
1202
|
}
|
|
1178
1203
|
};
|
|
1179
|
-
}
|
|
1204
|
+
})
|
|
1180
1205
|
);
|
|
1181
1206
|
}
|
|
1182
1207
|
|
|
1183
1208
|
// src/tools/test-brief.ts
|
|
1184
|
-
import { z as
|
|
1185
|
-
var projectIdSchema10 = z10.string().uuid().describe("The project UUID");
|
|
1209
|
+
import { z as z11 } from "zod";
|
|
1186
1210
|
async function settled2(p) {
|
|
1187
1211
|
try {
|
|
1188
1212
|
return await p;
|
|
1189
|
-
} catch {
|
|
1213
|
+
} catch (e) {
|
|
1190
1214
|
return null;
|
|
1191
1215
|
}
|
|
1192
1216
|
}
|
|
@@ -1197,14 +1221,14 @@ function registerTestBriefTools(server, client) {
|
|
|
1197
1221
|
title: "Test brief",
|
|
1198
1222
|
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
1223
|
inputSchema: {
|
|
1200
|
-
projectId:
|
|
1201
|
-
componentId:
|
|
1224
|
+
projectId: projectIdSchema,
|
|
1225
|
+
componentId: z11.string().describe('The component ID to write a test for (matches <Adaptive id="...">).')
|
|
1202
1226
|
},
|
|
1203
1227
|
outputSchema: {
|
|
1204
|
-
componentId:
|
|
1205
|
-
forcedVariantId:
|
|
1206
|
-
goalName:
|
|
1207
|
-
markdown:
|
|
1228
|
+
componentId: z11.string(),
|
|
1229
|
+
forcedVariantId: z11.string().describe("The non-control variant the example forces"),
|
|
1230
|
+
goalName: z11.string().describe("The goal the example asserts fires"),
|
|
1231
|
+
markdown: z11.string().describe("The full test brief in Markdown")
|
|
1208
1232
|
},
|
|
1209
1233
|
annotations: {
|
|
1210
1234
|
readOnlyHint: true,
|
|
@@ -1212,20 +1236,21 @@ function registerTestBriefTools(server, client) {
|
|
|
1212
1236
|
openWorldHint: false
|
|
1213
1237
|
}
|
|
1214
1238
|
},
|
|
1215
|
-
async ({ projectId, componentId }) => {
|
|
1239
|
+
withApiErrorGuidance(async ({ projectId, componentId }) => {
|
|
1240
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1216
1241
|
const id = encodeURIComponent(projectId);
|
|
1217
1242
|
const [componentsEnvelope, goalsRes] = await Promise.all([
|
|
1218
1243
|
// mgmt API returns a paginated envelope: { components, total, page, limit }.
|
|
1219
1244
|
settled2(client.get(`/projects/${id}/components`)),
|
|
1220
1245
|
settled2(client.get(`/projects/${id}/goals`))
|
|
1221
1246
|
]);
|
|
1222
|
-
const components = componentsEnvelope
|
|
1223
|
-
const component = components.find((c) => c.component_id === componentId)
|
|
1224
|
-
const variantIds = component
|
|
1225
|
-
const goals = Array.isArray(goalsRes) ? goalsRes : goalsRes
|
|
1226
|
-
const goalName = goals[0]
|
|
1227
|
-
const controlId = variantIds[0]
|
|
1228
|
-
const forcedId = variantIds.find((v) => v !== controlId)
|
|
1247
|
+
const components = (_a = componentsEnvelope == null ? void 0 : componentsEnvelope.components) != null ? _a : [];
|
|
1248
|
+
const component = (_b = components.find((c) => c.component_id === componentId)) != null ? _b : null;
|
|
1249
|
+
const variantIds = (_c = component == null ? void 0 : component.variants.map((v) => v.variant_id)) != null ? _c : [];
|
|
1250
|
+
const goals = Array.isArray(goalsRes) ? goalsRes : (_d = goalsRes == null ? void 0 : goalsRes.goals) != null ? _d : [];
|
|
1251
|
+
const goalName = (_f = (_e = goals[0]) == null ? void 0 : _e.goalName) != null ? _f : "signup";
|
|
1252
|
+
const controlId = (_g = variantIds[0]) != null ? _g : "control";
|
|
1253
|
+
const forcedId = (_h = variantIds.find((v) => v !== controlId)) != null ? _h : "variant_b";
|
|
1229
1254
|
const lines = [];
|
|
1230
1255
|
lines.push(`# Test brief \u2014 ${componentId}`);
|
|
1231
1256
|
lines.push("");
|
|
@@ -1281,12 +1306,12 @@ function registerTestBriefTools(server, client) {
|
|
|
1281
1306
|
content: [{ type: "text", text: markdown }],
|
|
1282
1307
|
structuredContent: { componentId, forcedVariantId: forcedId, goalName, markdown }
|
|
1283
1308
|
};
|
|
1284
|
-
}
|
|
1309
|
+
})
|
|
1285
1310
|
);
|
|
1286
1311
|
}
|
|
1287
1312
|
|
|
1288
1313
|
// src/tools/integration-guide.ts
|
|
1289
|
-
import { z as
|
|
1314
|
+
import { z as z12 } from "zod";
|
|
1290
1315
|
var GUIDE = `# SentientUI integration guide \u2014 the adaptive ladder
|
|
1291
1316
|
|
|
1292
1317
|
SentientUI adapts a site per visitor type (personas: buyer, researcher, deal_seeker, browser,
|
|
@@ -1361,7 +1386,7 @@ function registerIntegrationGuideTools(server) {
|
|
|
1361
1386
|
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
1387
|
inputSchema: {},
|
|
1363
1388
|
outputSchema: {
|
|
1364
|
-
guide:
|
|
1389
|
+
guide: z12.string().describe("The full integration guide in Markdown")
|
|
1365
1390
|
},
|
|
1366
1391
|
annotations: {
|
|
1367
1392
|
readOnlyHint: true,
|
|
@@ -1377,7 +1402,7 @@ function registerIntegrationGuideTools(server) {
|
|
|
1377
1402
|
}
|
|
1378
1403
|
|
|
1379
1404
|
// src/server.ts
|
|
1380
|
-
var
|
|
1405
|
+
var PKG_VERSION = true ? "0.8.2" : "0.0.0-dev";
|
|
1381
1406
|
function createMcpServer(client) {
|
|
1382
1407
|
const server = new McpServer(
|
|
1383
1408
|
{
|