@sentientui/mcp 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/{chunk-ZXI3OZKA.js → chunk-QH3RMHLM.js} +495 -86
- package/dist/index.cjs +495 -86
- package/dist/index.js +1 -1
- package/dist/lib.cjs +495 -86
- package/dist/lib.js +1 -1
- package/package.json +5 -4
package/dist/lib.cjs
CHANGED
|
@@ -103,14 +103,29 @@ function createProjectGuidance(err) {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
function registerProjectTools(server, client) {
|
|
106
|
-
server.
|
|
106
|
+
server.registerTool(
|
|
107
107
|
"create_project",
|
|
108
|
-
"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.",
|
|
109
108
|
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
title: "Create project",
|
|
110
|
+
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.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
name: import_zod.z.string().min(1).describe("Human-readable project name"),
|
|
113
|
+
contextType: import_zod.z.enum(["saas", "ecommerce", "marketing", "internal"]).optional().describe("What kind of product this is; defaults to saas"),
|
|
114
|
+
framework: import_zod.z.enum(["next-app", "next-pages", "react", "core"]).optional().describe("Frontend framework, used to tailor setup; defaults to next-app"),
|
|
115
|
+
websiteUrl: import_zod.z.string().optional().describe("Production site origin to allow-list so the SDK's events aren't origin-blocked on day one")
|
|
116
|
+
},
|
|
117
|
+
outputSchema: {
|
|
118
|
+
projectId: import_zod.z.string().describe("The new project UUID"),
|
|
119
|
+
publicKey: import_zod.z.string().describe("The pk_ public key to configure the SDK with"),
|
|
120
|
+
name: import_zod.z.string().describe("The project name"),
|
|
121
|
+
contextType: import_zod.z.string().describe("The resolved context type")
|
|
122
|
+
},
|
|
123
|
+
annotations: {
|
|
124
|
+
readOnlyHint: false,
|
|
125
|
+
destructiveHint: false,
|
|
126
|
+
idempotentHint: false,
|
|
127
|
+
openWorldHint: false
|
|
128
|
+
}
|
|
114
129
|
},
|
|
115
130
|
async ({ name, contextType, framework, websiteUrl }) => {
|
|
116
131
|
try {
|
|
@@ -120,15 +135,22 @@ function registerProjectTools(server, client) {
|
|
|
120
135
|
framework,
|
|
121
136
|
origin: websiteUrl
|
|
122
137
|
});
|
|
138
|
+
const resolvedContextType = contextType ?? "saas";
|
|
123
139
|
return {
|
|
124
140
|
content: [{
|
|
125
141
|
type: "text",
|
|
126
142
|
text: [
|
|
127
|
-
`Created project "${name}" (id: ${created.id}, type: ${
|
|
143
|
+
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
128
144
|
`Public key: ${created.apiKey}`,
|
|
129
145
|
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
130
146
|
].join("\n")
|
|
131
|
-
}]
|
|
147
|
+
}],
|
|
148
|
+
structuredContent: {
|
|
149
|
+
projectId: created.id,
|
|
150
|
+
publicKey: created.apiKey,
|
|
151
|
+
name,
|
|
152
|
+
contextType: resolvedContextType
|
|
153
|
+
}
|
|
132
154
|
};
|
|
133
155
|
} catch (err) {
|
|
134
156
|
if (err instanceof ApiError) {
|
|
@@ -141,22 +163,65 @@ function registerProjectTools(server, client) {
|
|
|
141
163
|
}
|
|
142
164
|
}
|
|
143
165
|
);
|
|
144
|
-
server.
|
|
166
|
+
server.registerTool(
|
|
145
167
|
"list_projects",
|
|
146
|
-
|
|
147
|
-
|
|
168
|
+
{
|
|
169
|
+
title: "List projects",
|
|
170
|
+
description: "List all SentientUI projects for the authenticated account.",
|
|
171
|
+
inputSchema: {},
|
|
172
|
+
outputSchema: {
|
|
173
|
+
projects: import_zod.z.array(
|
|
174
|
+
import_zod.z.object({
|
|
175
|
+
id: import_zod.z.string().describe("Project UUID"),
|
|
176
|
+
name: import_zod.z.string(),
|
|
177
|
+
contextType: import_zod.z.string(),
|
|
178
|
+
createdAt: import_zod.z.string().describe("ISO date (YYYY-MM-DD)")
|
|
179
|
+
})
|
|
180
|
+
).describe("All projects for the account (empty if none)")
|
|
181
|
+
},
|
|
182
|
+
annotations: {
|
|
183
|
+
readOnlyHint: true,
|
|
184
|
+
idempotentHint: true,
|
|
185
|
+
openWorldHint: false
|
|
186
|
+
}
|
|
187
|
+
},
|
|
148
188
|
async () => {
|
|
149
189
|
const projects = await client.get("/projects");
|
|
150
190
|
const text = projects.length === 0 ? "No projects found." : projects.map(
|
|
151
191
|
(p) => `- ${p.name} (id: ${p.id}, type: ${p.context_type}, created: ${p.created_at.slice(0, 10)})`
|
|
152
192
|
).join("\n");
|
|
153
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
content: [{ type: "text", text }],
|
|
195
|
+
structuredContent: {
|
|
196
|
+
projects: projects.map((p) => ({
|
|
197
|
+
id: p.id,
|
|
198
|
+
name: p.name,
|
|
199
|
+
contextType: p.context_type,
|
|
200
|
+
createdAt: p.created_at.slice(0, 10)
|
|
201
|
+
}))
|
|
202
|
+
}
|
|
203
|
+
};
|
|
154
204
|
}
|
|
155
205
|
);
|
|
156
|
-
server.
|
|
206
|
+
server.registerTool(
|
|
157
207
|
"get_project_stats",
|
|
158
|
-
|
|
159
|
-
|
|
208
|
+
{
|
|
209
|
+
title: "Project health stats",
|
|
210
|
+
description: "Get health stats for a project: event volume, session count, agent calls, and status.",
|
|
211
|
+
inputSchema: { projectId: projectIdSchema },
|
|
212
|
+
outputSchema: {
|
|
213
|
+
status: import_zod.z.string().describe("Overall project health status"),
|
|
214
|
+
events24h: import_zod.z.number().describe("Events in the last 24 hours"),
|
|
215
|
+
sessions24h: import_zod.z.number().describe("Sessions in the last 24 hours"),
|
|
216
|
+
agentCalls: import_zod.z.number().describe("Total agent (MCP/API) calls"),
|
|
217
|
+
lastEventAt: import_zod.z.string().nullable().describe("ISO timestamp of the last event, or null")
|
|
218
|
+
},
|
|
219
|
+
annotations: {
|
|
220
|
+
readOnlyHint: true,
|
|
221
|
+
idempotentHint: true,
|
|
222
|
+
openWorldHint: false
|
|
223
|
+
}
|
|
224
|
+
},
|
|
160
225
|
async ({ projectId }) => {
|
|
161
226
|
const id = encodeURIComponent(projectId);
|
|
162
227
|
const stats = await client.get(`/projects/${id}/health`);
|
|
@@ -167,7 +232,16 @@ function registerProjectTools(server, client) {
|
|
|
167
232
|
`Agent calls (total): ${stats.agentCalls}`,
|
|
168
233
|
`Last event: ${stats.lastEventAt ?? "never"}`
|
|
169
234
|
].join("\n");
|
|
170
|
-
return {
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: "text", text }],
|
|
237
|
+
structuredContent: {
|
|
238
|
+
status: stats.status,
|
|
239
|
+
events24h: stats.events24h,
|
|
240
|
+
sessions24h: stats.sessions24h,
|
|
241
|
+
agentCalls: stats.agentCalls,
|
|
242
|
+
lastEventAt: stats.lastEventAt
|
|
243
|
+
}
|
|
244
|
+
};
|
|
171
245
|
}
|
|
172
246
|
);
|
|
173
247
|
}
|
|
@@ -176,37 +250,97 @@ function registerProjectTools(server, client) {
|
|
|
176
250
|
var import_zod2 = require("zod");
|
|
177
251
|
var projectIdSchema2 = import_zod2.z.string().uuid().describe("The project UUID");
|
|
178
252
|
function registerComponentTools(server, client) {
|
|
179
|
-
server.
|
|
253
|
+
server.registerTool(
|
|
180
254
|
"list_components",
|
|
181
|
-
|
|
182
|
-
|
|
255
|
+
{
|
|
256
|
+
title: "List components",
|
|
257
|
+
description: "List all adaptive components in a project with variant counts and impression totals.",
|
|
258
|
+
inputSchema: { projectId: projectIdSchema2 },
|
|
259
|
+
outputSchema: {
|
|
260
|
+
components: import_zod2.z.array(
|
|
261
|
+
import_zod2.z.object({
|
|
262
|
+
componentId: import_zod2.z.string(),
|
|
263
|
+
variantCount: import_zod2.z.number(),
|
|
264
|
+
impressions: import_zod2.z.number(),
|
|
265
|
+
conversions: import_zod2.z.number()
|
|
266
|
+
})
|
|
267
|
+
).describe("Adaptive components in the project (empty if none)")
|
|
268
|
+
},
|
|
269
|
+
annotations: {
|
|
270
|
+
readOnlyHint: true,
|
|
271
|
+
idempotentHint: true,
|
|
272
|
+
openWorldHint: false
|
|
273
|
+
}
|
|
274
|
+
},
|
|
183
275
|
async ({ projectId }) => {
|
|
184
276
|
const id = encodeURIComponent(projectId);
|
|
185
277
|
const components = await client.get(`/projects/${id}/components`);
|
|
278
|
+
const structuredContent = {
|
|
279
|
+
components: components.map((c) => ({
|
|
280
|
+
componentId: c.component_id,
|
|
281
|
+
variantCount: c.variants.length,
|
|
282
|
+
impressions: c.total_impressions,
|
|
283
|
+
conversions: c.total_conversions
|
|
284
|
+
}))
|
|
285
|
+
};
|
|
186
286
|
if (!components.length) {
|
|
187
|
-
return {
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: "text", text: "No components found for this project." }],
|
|
289
|
+
structuredContent
|
|
290
|
+
};
|
|
188
291
|
}
|
|
189
292
|
const text = components.map(
|
|
190
293
|
(c) => `- ${c.component_id}: ${c.variants.length} variants, ${c.total_impressions} impressions, ${c.total_conversions} conversions`
|
|
191
294
|
).join("\n");
|
|
192
|
-
return { content: [{ type: "text", text }] };
|
|
295
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
193
296
|
}
|
|
194
297
|
);
|
|
195
|
-
server.
|
|
298
|
+
server.registerTool(
|
|
196
299
|
"get_variant_performance",
|
|
197
|
-
|
|
198
|
-
|
|
300
|
+
{
|
|
301
|
+
title: "Variant performance",
|
|
302
|
+
description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
|
|
303
|
+
inputSchema: { projectId: projectIdSchema2 },
|
|
304
|
+
outputSchema: {
|
|
305
|
+
variants: import_zod2.z.array(
|
|
306
|
+
import_zod2.z.object({
|
|
307
|
+
variantId: import_zod2.z.string(),
|
|
308
|
+
currentCvr: import_zod2.z.number().describe("Conversion rate over the last 7 days (0-1)"),
|
|
309
|
+
priorCvr: import_zod2.z.number().describe("Conversion rate over the prior 7 days (0-1)"),
|
|
310
|
+
deltaPp: import_zod2.z.number().describe("Change in percentage points"),
|
|
311
|
+
momentum: import_zod2.z.string().describe("Momentum direction: gaining, losing, or stable")
|
|
312
|
+
})
|
|
313
|
+
).describe("Per-variant performance (empty if no data yet)")
|
|
314
|
+
},
|
|
315
|
+
annotations: {
|
|
316
|
+
readOnlyHint: true,
|
|
317
|
+
idempotentHint: true,
|
|
318
|
+
openWorldHint: false
|
|
319
|
+
}
|
|
320
|
+
},
|
|
199
321
|
async ({ projectId }) => {
|
|
200
322
|
const id = encodeURIComponent(projectId);
|
|
201
323
|
const data = await client.get(`/projects/${id}/trends`);
|
|
324
|
+
const momentumMap = new Map((data.momentum ?? []).map((m) => [m.variantId, m.direction]));
|
|
325
|
+
const structuredContent = {
|
|
326
|
+
variants: (data.cvr ?? []).map((v) => ({
|
|
327
|
+
variantId: v.variantId,
|
|
328
|
+
currentCvr: v.currentCvr,
|
|
329
|
+
priorCvr: v.priorCvr,
|
|
330
|
+
deltaPp: v.deltaPp,
|
|
331
|
+
momentum: momentumMap.get(v.variantId) ?? "stable"
|
|
332
|
+
}))
|
|
333
|
+
};
|
|
202
334
|
if (!data.cvr?.length) {
|
|
203
|
-
return {
|
|
335
|
+
return {
|
|
336
|
+
content: [{ type: "text", text: "No variant data available yet." }],
|
|
337
|
+
structuredContent
|
|
338
|
+
};
|
|
204
339
|
}
|
|
205
|
-
const momentumMap = new Map(data.momentum.map((m) => [m.variantId, m.direction]));
|
|
206
340
|
const text = data.cvr.map(
|
|
207
341
|
(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"})`
|
|
208
342
|
).join("\n");
|
|
209
|
-
return { content: [{ type: "text", text }] };
|
|
343
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
210
344
|
}
|
|
211
345
|
);
|
|
212
346
|
}
|
|
@@ -215,28 +349,63 @@ function registerComponentTools(server, client) {
|
|
|
215
349
|
var import_zod3 = require("zod");
|
|
216
350
|
var projectIdSchema3 = import_zod3.z.string().uuid().describe("The project UUID");
|
|
217
351
|
function registerInsightTools(server, client) {
|
|
218
|
-
server.
|
|
352
|
+
server.registerTool(
|
|
219
353
|
"get_insights",
|
|
220
|
-
|
|
221
|
-
|
|
354
|
+
{
|
|
355
|
+
title: "Get insights",
|
|
356
|
+
description: "Get the latest AI-generated insights: narrator observations and (Growth tier) advisor recommendations.",
|
|
357
|
+
inputSchema: { projectId: projectIdSchema3 },
|
|
358
|
+
outputSchema: {
|
|
359
|
+
status: import_zod3.z.enum(["ok", "empty"]).describe("Whether insights exist yet"),
|
|
360
|
+
observations: import_zod3.z.array(import_zod3.z.string()).describe("Narrator observations"),
|
|
361
|
+
recommendations: import_zod3.z.array(import_zod3.z.string()).describe("Advisor recommendations (Growth tier)"),
|
|
362
|
+
isStale: import_zod3.z.boolean().describe("True when the insights are older than ~6h"),
|
|
363
|
+
generatedAt: import_zod3.z.string().nullable().describe("ISO timestamp the insights were generated, or null")
|
|
364
|
+
},
|
|
365
|
+
annotations: {
|
|
366
|
+
readOnlyHint: true,
|
|
367
|
+
idempotentHint: true,
|
|
368
|
+
openWorldHint: false
|
|
369
|
+
}
|
|
370
|
+
},
|
|
222
371
|
async ({ projectId }) => {
|
|
223
372
|
const id = encodeURIComponent(projectId);
|
|
224
373
|
const data = await client.get(`/projects/${id}/insights`);
|
|
225
374
|
if (data.status === "empty") {
|
|
226
|
-
return {
|
|
375
|
+
return {
|
|
376
|
+
content: [{ type: "text", text: "No insights generated yet. Use refresh_insights to generate." }],
|
|
377
|
+
structuredContent: {
|
|
378
|
+
status: "empty",
|
|
379
|
+
observations: [],
|
|
380
|
+
recommendations: [],
|
|
381
|
+
isStale: false,
|
|
382
|
+
generatedAt: null
|
|
383
|
+
}
|
|
384
|
+
};
|
|
227
385
|
}
|
|
386
|
+
const observations = data.narratorBullets ?? [];
|
|
387
|
+
const recommendations = data.advisorBullets ?? [];
|
|
228
388
|
const lines = [];
|
|
229
389
|
if (data.isStale) lines.push("\u26A0 Insights are stale (>6h old). Consider calling refresh_insights.");
|
|
230
390
|
if (data.generatedAt) lines.push(`Generated: ${new Date(data.generatedAt).toUTCString()}`);
|
|
231
391
|
lines.push("");
|
|
232
392
|
lines.push("Observations:");
|
|
233
|
-
|
|
234
|
-
if (
|
|
393
|
+
observations.forEach((b) => lines.push(`- ${b}`));
|
|
394
|
+
if (recommendations.length) {
|
|
235
395
|
lines.push("");
|
|
236
396
|
lines.push("Recommendations:");
|
|
237
|
-
|
|
397
|
+
recommendations.forEach((b) => lines.push(`- ${b}`));
|
|
238
398
|
}
|
|
239
|
-
return {
|
|
399
|
+
return {
|
|
400
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
401
|
+
structuredContent: {
|
|
402
|
+
status: "ok",
|
|
403
|
+
observations,
|
|
404
|
+
recommendations,
|
|
405
|
+
isStale: data.isStale ?? false,
|
|
406
|
+
generatedAt: data.generatedAt ?? null
|
|
407
|
+
}
|
|
408
|
+
};
|
|
240
409
|
}
|
|
241
410
|
);
|
|
242
411
|
}
|
|
@@ -245,15 +414,46 @@ function registerInsightTools(server, client) {
|
|
|
245
414
|
var import_zod4 = require("zod");
|
|
246
415
|
var projectIdSchema4 = import_zod4.z.string().uuid().describe("The project UUID");
|
|
247
416
|
function registerPersonaTools(server, client) {
|
|
248
|
-
server.
|
|
417
|
+
server.registerTool(
|
|
249
418
|
"get_persona_breakdown",
|
|
250
|
-
|
|
251
|
-
|
|
419
|
+
{
|
|
420
|
+
title: "Persona breakdown",
|
|
421
|
+
description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
|
|
422
|
+
inputSchema: { projectId: projectIdSchema4 },
|
|
423
|
+
outputSchema: {
|
|
424
|
+
totalSessions: import_zod4.z.number().describe("Total sessions across all clusters"),
|
|
425
|
+
clusters: import_zod4.z.array(
|
|
426
|
+
import_zod4.z.object({
|
|
427
|
+
label: import_zod4.z.string(),
|
|
428
|
+
sessionCount: import_zod4.z.number(),
|
|
429
|
+
sharePct: import_zod4.z.number().describe("Share of total traffic (0-100)"),
|
|
430
|
+
reliability: import_zod4.z.number().describe("Average cluster reliability (0-1)")
|
|
431
|
+
})
|
|
432
|
+
).describe("Persona clusters (empty until enough visitor data)")
|
|
433
|
+
},
|
|
434
|
+
annotations: {
|
|
435
|
+
readOnlyHint: true,
|
|
436
|
+
idempotentHint: true,
|
|
437
|
+
openWorldHint: false
|
|
438
|
+
}
|
|
439
|
+
},
|
|
252
440
|
async ({ projectId }) => {
|
|
253
441
|
const id = encodeURIComponent(projectId);
|
|
254
442
|
const data = await client.get(`/projects/${id}/portraits`);
|
|
443
|
+
const structuredContent = {
|
|
444
|
+
totalSessions: data.totalSessions,
|
|
445
|
+
clusters: data.clusters.map((c) => ({
|
|
446
|
+
label: c.label,
|
|
447
|
+
sessionCount: c.sessionCount,
|
|
448
|
+
sharePct: data.totalSessions > 0 ? c.sessionCount / data.totalSessions * 100 : 0,
|
|
449
|
+
reliability: c.avgReliability
|
|
450
|
+
}))
|
|
451
|
+
};
|
|
255
452
|
if (!data.clusters.length) {
|
|
256
|
-
return {
|
|
453
|
+
return {
|
|
454
|
+
content: [{ type: "text", text: "No persona clusters yet. More visitor data is needed." }],
|
|
455
|
+
structuredContent
|
|
456
|
+
};
|
|
257
457
|
}
|
|
258
458
|
const lines = [
|
|
259
459
|
`Total sessions: ${data.totalSessions}`,
|
|
@@ -264,7 +464,7 @@ function registerPersonaTools(server, client) {
|
|
|
264
464
|
return `- ${c.label}: ${c.sessionCount} sessions (${pct.toFixed(1)}% of traffic, reliability ${(c.avgReliability * 100).toFixed(0)}%)`;
|
|
265
465
|
})
|
|
266
466
|
];
|
|
267
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
467
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
268
468
|
}
|
|
269
469
|
);
|
|
270
470
|
}
|
|
@@ -273,22 +473,63 @@ function registerPersonaTools(server, client) {
|
|
|
273
473
|
var import_zod5 = require("zod");
|
|
274
474
|
var projectIdSchema5 = import_zod5.z.string().uuid().describe("The project UUID");
|
|
275
475
|
function registerGoalTools(server, client) {
|
|
276
|
-
server.
|
|
476
|
+
server.registerTool(
|
|
277
477
|
"get_goal_funnel",
|
|
278
|
-
|
|
279
|
-
|
|
478
|
+
{
|
|
479
|
+
title: "Goal funnel",
|
|
480
|
+
description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
|
|
481
|
+
inputSchema: { projectId: projectIdSchema5 },
|
|
482
|
+
outputSchema: {
|
|
483
|
+
goals: import_zod5.z.array(
|
|
484
|
+
import_zod5.z.object({
|
|
485
|
+
goalName: import_zod5.z.string(),
|
|
486
|
+
hits: import_zod5.z.number(),
|
|
487
|
+
uniqueSessions: import_zod5.z.number(),
|
|
488
|
+
conversionRate: import_zod5.z.number().describe("Unique-session conversion rate (0-1)"),
|
|
489
|
+
variants: import_zod5.z.array(
|
|
490
|
+
import_zod5.z.object({
|
|
491
|
+
componentId: import_zod5.z.string(),
|
|
492
|
+
variantId: import_zod5.z.string(),
|
|
493
|
+
completionRate: import_zod5.z.number().describe("Completion rate per assigned session (0-1)")
|
|
494
|
+
})
|
|
495
|
+
).describe("Per-variant breakdown")
|
|
496
|
+
})
|
|
497
|
+
).describe("Configured goals (empty if none)")
|
|
498
|
+
},
|
|
499
|
+
annotations: {
|
|
500
|
+
readOnlyHint: true,
|
|
501
|
+
idempotentHint: true,
|
|
502
|
+
openWorldHint: false
|
|
503
|
+
}
|
|
504
|
+
},
|
|
280
505
|
async ({ projectId }) => {
|
|
281
506
|
const id = encodeURIComponent(projectId);
|
|
282
507
|
const data = await client.get(`/projects/${id}/goals`);
|
|
508
|
+
const structuredContent = {
|
|
509
|
+
goals: data.goals.map((g) => ({
|
|
510
|
+
goalName: g.goalName,
|
|
511
|
+
hits: g.hits,
|
|
512
|
+
uniqueSessions: g.uniqueSessions,
|
|
513
|
+
conversionRate: g.pct,
|
|
514
|
+
variants: g.variants.map((v) => ({
|
|
515
|
+
componentId: v.componentId,
|
|
516
|
+
variantId: v.variantId,
|
|
517
|
+
completionRate: v.completionRate
|
|
518
|
+
}))
|
|
519
|
+
}))
|
|
520
|
+
};
|
|
283
521
|
if (!data.goals.length) {
|
|
284
|
-
return {
|
|
522
|
+
return {
|
|
523
|
+
content: [{ type: "text", text: "No goals configured for this project." }],
|
|
524
|
+
structuredContent
|
|
525
|
+
};
|
|
285
526
|
}
|
|
286
527
|
const lines = data.goals.flatMap((g) => [
|
|
287
528
|
`${g.goalName}: ${g.hits} hits, ${g.uniqueSessions} unique sessions, ${(g.pct * 100).toFixed(1)}% conversion`,
|
|
288
529
|
...g.variants.map((v) => ` ${v.componentId}/${v.variantId}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
|
|
289
530
|
""
|
|
290
531
|
]);
|
|
291
|
-
return { content: [{ type: "text", text: lines.join("\n").trim() }] };
|
|
532
|
+
return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
|
|
292
533
|
}
|
|
293
534
|
);
|
|
294
535
|
}
|
|
@@ -297,20 +538,47 @@ function registerGoalTools(server, client) {
|
|
|
297
538
|
var import_zod6 = require("zod");
|
|
298
539
|
var projectIdSchema6 = import_zod6.z.string().uuid().describe("The project UUID");
|
|
299
540
|
function registerGuardrailTools(server, client) {
|
|
300
|
-
server.
|
|
541
|
+
server.registerTool(
|
|
301
542
|
"list_guardrail_events",
|
|
302
|
-
|
|
303
|
-
|
|
543
|
+
{
|
|
544
|
+
title: "List guardrail events",
|
|
545
|
+
description: "List variants currently paused by the guardrail in the last 24 hours.",
|
|
546
|
+
inputSchema: { projectId: projectIdSchema6 },
|
|
547
|
+
outputSchema: {
|
|
548
|
+
events: import_zod6.z.array(
|
|
549
|
+
import_zod6.z.object({
|
|
550
|
+
componentId: import_zod6.z.string(),
|
|
551
|
+
variantIds: import_zod6.z.array(import_zod6.z.string()).describe("Variants paused by the guardrail"),
|
|
552
|
+
pausedAt: import_zod6.z.string().nullable().describe("ISO timestamp the pause fired, or null")
|
|
553
|
+
})
|
|
554
|
+
).describe("Guardrail events in the last 24h (empty if none)")
|
|
555
|
+
},
|
|
556
|
+
annotations: {
|
|
557
|
+
readOnlyHint: true,
|
|
558
|
+
idempotentHint: true,
|
|
559
|
+
openWorldHint: false
|
|
560
|
+
}
|
|
561
|
+
},
|
|
304
562
|
async ({ projectId }) => {
|
|
305
563
|
const id = encodeURIComponent(projectId);
|
|
306
564
|
const data = await client.get(`/projects/${id}/guardrail-events`);
|
|
565
|
+
const structuredContent = {
|
|
566
|
+
events: data.guardrailEvents.map((e) => ({
|
|
567
|
+
componentId: e.componentId,
|
|
568
|
+
variantIds: e.variantIds,
|
|
569
|
+
pausedAt: e.pausedAt
|
|
570
|
+
}))
|
|
571
|
+
};
|
|
307
572
|
if (!data.guardrailEvents.length) {
|
|
308
|
-
return {
|
|
573
|
+
return {
|
|
574
|
+
content: [{ type: "text", text: "No active guardrail events in the last 24 hours." }],
|
|
575
|
+
structuredContent
|
|
576
|
+
};
|
|
309
577
|
}
|
|
310
578
|
const lines = data.guardrailEvents.map(
|
|
311
579
|
(e) => `- ${e.componentId}: variants [${e.variantIds.join(", ")}] paused${e.pausedAt ? ` at ${e.pausedAt}` : ""}`
|
|
312
580
|
);
|
|
313
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
581
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
314
582
|
}
|
|
315
583
|
);
|
|
316
584
|
}
|
|
@@ -319,20 +587,49 @@ function registerGuardrailTools(server, client) {
|
|
|
319
587
|
var import_zod7 = require("zod");
|
|
320
588
|
var projectIdSchema7 = import_zod7.z.string().uuid().describe("The project UUID");
|
|
321
589
|
function registerLayoutTools(server, client) {
|
|
322
|
-
server.
|
|
590
|
+
server.registerTool(
|
|
323
591
|
"get_layout_stats",
|
|
324
|
-
|
|
325
|
-
|
|
592
|
+
{
|
|
593
|
+
title: "Layout stats",
|
|
594
|
+
description: "Get per-persona section layout rankings and bandit reward weights.",
|
|
595
|
+
inputSchema: { projectId: projectIdSchema7 },
|
|
596
|
+
outputSchema: {
|
|
597
|
+
layouts: import_zod7.z.array(
|
|
598
|
+
import_zod7.z.object({
|
|
599
|
+
persona: import_zod7.z.string(),
|
|
600
|
+
layoutOrder: import_zod7.z.array(import_zod7.z.string()).describe("Ranked section order for this persona"),
|
|
601
|
+
pulls: import_zod7.z.number().describe("Number of times this arm was served"),
|
|
602
|
+
avgReward: import_zod7.z.number().describe("Average bandit reward weight")
|
|
603
|
+
})
|
|
604
|
+
).describe("Per-persona layout rankings (empty until enough sessions)")
|
|
605
|
+
},
|
|
606
|
+
annotations: {
|
|
607
|
+
readOnlyHint: true,
|
|
608
|
+
idempotentHint: true,
|
|
609
|
+
openWorldHint: false
|
|
610
|
+
}
|
|
611
|
+
},
|
|
326
612
|
async ({ projectId }) => {
|
|
327
613
|
const id = encodeURIComponent(projectId);
|
|
328
614
|
const stats = await client.get(`/projects/${id}/layout-stats`);
|
|
615
|
+
const structuredContent = {
|
|
616
|
+
layouts: stats.map((s) => ({
|
|
617
|
+
persona: s.persona,
|
|
618
|
+
layoutOrder: s.layoutOrder,
|
|
619
|
+
pulls: s.pulls,
|
|
620
|
+
avgReward: s.avgReward
|
|
621
|
+
}))
|
|
622
|
+
};
|
|
329
623
|
if (!stats.length) {
|
|
330
|
-
return {
|
|
624
|
+
return {
|
|
625
|
+
content: [{ type: "text", text: "No layout data yet. More visitor sessions are needed." }],
|
|
626
|
+
structuredContent
|
|
627
|
+
};
|
|
331
628
|
}
|
|
332
629
|
const text = stats.map(
|
|
333
630
|
(s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
|
|
334
631
|
).join("\n");
|
|
335
|
-
return { content: [{ type: "text", text }] };
|
|
632
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
336
633
|
}
|
|
337
634
|
);
|
|
338
635
|
}
|
|
@@ -341,14 +638,30 @@ function registerLayoutTools(server, client) {
|
|
|
341
638
|
var import_zod8 = require("zod");
|
|
342
639
|
var projectIdSchema8 = import_zod8.z.string().uuid().describe("The project UUID");
|
|
343
640
|
function registerVariantWriteTools(server, client) {
|
|
344
|
-
server.
|
|
641
|
+
server.registerTool(
|
|
345
642
|
"create_variant",
|
|
346
|
-
"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).",
|
|
347
643
|
{
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
644
|
+
title: "Create managed variant",
|
|
645
|
+
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).",
|
|
646
|
+
inputSchema: {
|
|
647
|
+
projectId: projectIdSchema8,
|
|
648
|
+
componentId: import_zod8.z.string().describe("The component ID to add a variant to"),
|
|
649
|
+
displayName: import_zod8.z.string().describe("Human-readable name for the new variant"),
|
|
650
|
+
content: import_zod8.z.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.")
|
|
651
|
+
},
|
|
652
|
+
outputSchema: {
|
|
653
|
+
variantId: import_zod8.z.string().describe("The new variant ID"),
|
|
654
|
+
displayName: import_zod8.z.string(),
|
|
655
|
+
componentId: import_zod8.z.string(),
|
|
656
|
+
state: import_zod8.z.literal("draft").describe("New managed variants start in draft state"),
|
|
657
|
+
hasContent: import_zod8.z.boolean().describe("Whether text content was provided at creation")
|
|
658
|
+
},
|
|
659
|
+
annotations: {
|
|
660
|
+
readOnlyHint: false,
|
|
661
|
+
destructiveHint: false,
|
|
662
|
+
idempotentHint: false,
|
|
663
|
+
openWorldHint: false
|
|
664
|
+
}
|
|
352
665
|
},
|
|
353
666
|
async ({ projectId, componentId, displayName, content }) => {
|
|
354
667
|
const id = encodeURIComponent(projectId);
|
|
@@ -361,17 +674,38 @@ function registerVariantWriteTools(server, client) {
|
|
|
361
674
|
content: [{
|
|
362
675
|
type: "text",
|
|
363
676
|
text: `Managed text variant created: ${result.variantId} ("${result.displayName}") for component ${componentId}${contentNote}. It is in draft state \u2014 activate it from the dashboard. Reminder: this is a no-code managed variant; for code-native variants, edit the code instead (see get_variant_brief).`
|
|
364
|
-
}]
|
|
677
|
+
}],
|
|
678
|
+
structuredContent: {
|
|
679
|
+
variantId: result.variantId,
|
|
680
|
+
displayName: result.displayName,
|
|
681
|
+
componentId,
|
|
682
|
+
state: "draft",
|
|
683
|
+
hasContent: Boolean(content)
|
|
684
|
+
}
|
|
365
685
|
};
|
|
366
686
|
}
|
|
367
687
|
);
|
|
368
|
-
server.
|
|
688
|
+
server.registerTool(
|
|
369
689
|
"pause_variant",
|
|
370
|
-
"Pause a variant, stopping traffic from being assigned to it.",
|
|
371
690
|
{
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
691
|
+
title: "Pause variant",
|
|
692
|
+
description: "Pause a variant, stopping traffic from being assigned to it.",
|
|
693
|
+
inputSchema: {
|
|
694
|
+
projectId: projectIdSchema8,
|
|
695
|
+
componentId: import_zod8.z.string().describe("The component ID"),
|
|
696
|
+
variantId: import_zod8.z.string().describe("The variant ID to pause")
|
|
697
|
+
},
|
|
698
|
+
outputSchema: {
|
|
699
|
+
variantId: import_zod8.z.string(),
|
|
700
|
+
componentId: import_zod8.z.string(),
|
|
701
|
+
paused: import_zod8.z.literal(true).describe("The variant is now paused")
|
|
702
|
+
},
|
|
703
|
+
annotations: {
|
|
704
|
+
readOnlyHint: false,
|
|
705
|
+
destructiveHint: false,
|
|
706
|
+
idempotentHint: true,
|
|
707
|
+
openWorldHint: false
|
|
708
|
+
}
|
|
375
709
|
},
|
|
376
710
|
async ({ projectId, componentId, variantId }) => {
|
|
377
711
|
const id = encodeURIComponent(projectId);
|
|
@@ -380,14 +714,28 @@ function registerVariantWriteTools(server, client) {
|
|
|
380
714
|
content: [{
|
|
381
715
|
type: "text",
|
|
382
716
|
text: `Variant ${variantId} in component ${componentId} has been paused. No new traffic will be assigned to it.`
|
|
383
|
-
}]
|
|
717
|
+
}],
|
|
718
|
+
structuredContent: { variantId, componentId, paused: true }
|
|
384
719
|
};
|
|
385
720
|
}
|
|
386
721
|
);
|
|
387
|
-
server.
|
|
722
|
+
server.registerTool(
|
|
388
723
|
"refresh_insights",
|
|
389
|
-
|
|
390
|
-
|
|
724
|
+
{
|
|
725
|
+
title: "Refresh insights",
|
|
726
|
+
description: "Trigger fresh AI insight generation for a project. Returns immediately; use get_insights in ~15 seconds to see results.",
|
|
727
|
+
inputSchema: { projectId: projectIdSchema8 },
|
|
728
|
+
outputSchema: {
|
|
729
|
+
projectId: import_zod8.z.string(),
|
|
730
|
+
status: import_zod8.z.literal("generating").describe("Generation has been triggered")
|
|
731
|
+
},
|
|
732
|
+
annotations: {
|
|
733
|
+
readOnlyHint: false,
|
|
734
|
+
destructiveHint: false,
|
|
735
|
+
idempotentHint: false,
|
|
736
|
+
openWorldHint: false
|
|
737
|
+
}
|
|
738
|
+
},
|
|
391
739
|
async ({ projectId }) => {
|
|
392
740
|
const id = encodeURIComponent(projectId);
|
|
393
741
|
await client.post(`/projects/${id}/insights/refresh`);
|
|
@@ -395,7 +743,8 @@ function registerVariantWriteTools(server, client) {
|
|
|
395
743
|
content: [{
|
|
396
744
|
type: "text",
|
|
397
745
|
text: `Insights are generating for project ${projectId}. Call get_insights in ~15 seconds to see the results.`
|
|
398
|
-
}]
|
|
746
|
+
}],
|
|
747
|
+
structuredContent: { projectId, status: "generating" }
|
|
399
748
|
};
|
|
400
749
|
}
|
|
401
750
|
);
|
|
@@ -468,12 +817,28 @@ async function settled(p) {
|
|
|
468
817
|
}
|
|
469
818
|
}
|
|
470
819
|
function registerVariantBriefTools(server, client) {
|
|
471
|
-
server.
|
|
820
|
+
server.registerTool(
|
|
472
821
|
"get_variant_brief",
|
|
473
|
-
"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.",
|
|
474
822
|
{
|
|
475
|
-
|
|
476
|
-
|
|
823
|
+
title: "Variant brief",
|
|
824
|
+
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.",
|
|
825
|
+
inputSchema: {
|
|
826
|
+
projectId: projectIdSchema9,
|
|
827
|
+
componentId: import_zod9.z.string().describe('The component ID to write a new variant for (matches <Adaptive id="...">).')
|
|
828
|
+
},
|
|
829
|
+
outputSchema: {
|
|
830
|
+
componentId: import_zod9.z.string(),
|
|
831
|
+
contextType: import_zod9.z.string().describe("The project's context type (or 'unknown')"),
|
|
832
|
+
dataState: import_zod9.z.enum(["sufficient", "collecting", "empty"]).describe("Data-sufficiency assessment"),
|
|
833
|
+
existingVariantIds: import_zod9.z.array(import_zod9.z.string()).describe("Variant IDs already in use (do not reuse)"),
|
|
834
|
+
priors: import_zod9.z.array(import_zod9.z.string()).describe("Best-practice priors applied for this context type"),
|
|
835
|
+
markdown: import_zod9.z.string().describe("The full variant brief in Markdown")
|
|
836
|
+
},
|
|
837
|
+
annotations: {
|
|
838
|
+
readOnlyHint: true,
|
|
839
|
+
idempotentHint: true,
|
|
840
|
+
openWorldHint: false
|
|
841
|
+
}
|
|
477
842
|
},
|
|
478
843
|
async ({ projectId, componentId }) => {
|
|
479
844
|
const id = encodeURIComponent(projectId);
|
|
@@ -564,7 +929,18 @@ function registerVariantBriefTools(server, client) {
|
|
|
564
929
|
lines.push("5. Commit, push, and deploy. Optionally enable shadow mode for this component first if you want to validate before serving real traffic.");
|
|
565
930
|
lines.push("");
|
|
566
931
|
lines.push("Make the change reflect the data sufficiency above: data-driven when SUFFICIENT, best-practice-led when COLLECTING or EMPTY.");
|
|
567
|
-
|
|
932
|
+
const markdown = lines.join("\n");
|
|
933
|
+
return {
|
|
934
|
+
content: [{ type: "text", text: markdown }],
|
|
935
|
+
structuredContent: {
|
|
936
|
+
componentId,
|
|
937
|
+
contextType,
|
|
938
|
+
dataState,
|
|
939
|
+
existingVariantIds,
|
|
940
|
+
priors: priorsFor(contextType),
|
|
941
|
+
markdown
|
|
942
|
+
}
|
|
943
|
+
};
|
|
568
944
|
}
|
|
569
945
|
);
|
|
570
946
|
}
|
|
@@ -580,12 +956,26 @@ async function settled2(p) {
|
|
|
580
956
|
}
|
|
581
957
|
}
|
|
582
958
|
function registerTestBriefTools(server, client) {
|
|
583
|
-
server.
|
|
959
|
+
server.registerTool(
|
|
584
960
|
"get_test_brief",
|
|
585
|
-
"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).",
|
|
586
961
|
{
|
|
587
|
-
|
|
588
|
-
|
|
962
|
+
title: "Test brief",
|
|
963
|
+
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).",
|
|
964
|
+
inputSchema: {
|
|
965
|
+
projectId: projectIdSchema10,
|
|
966
|
+
componentId: import_zod10.z.string().describe('The component ID to write a test for (matches <Adaptive id="...">).')
|
|
967
|
+
},
|
|
968
|
+
outputSchema: {
|
|
969
|
+
componentId: import_zod10.z.string(),
|
|
970
|
+
forcedVariantId: import_zod10.z.string().describe("The non-control variant the example forces"),
|
|
971
|
+
goalName: import_zod10.z.string().describe("The goal the example asserts fires"),
|
|
972
|
+
markdown: import_zod10.z.string().describe("The full test brief in Markdown")
|
|
973
|
+
},
|
|
974
|
+
annotations: {
|
|
975
|
+
readOnlyHint: true,
|
|
976
|
+
idempotentHint: true,
|
|
977
|
+
openWorldHint: false
|
|
978
|
+
}
|
|
589
979
|
},
|
|
590
980
|
async ({ projectId, componentId }) => {
|
|
591
981
|
const id = encodeURIComponent(projectId);
|
|
@@ -649,12 +1039,17 @@ function registerTestBriefTools(server, client) {
|
|
|
649
1039
|
lines.push("```ts");
|
|
650
1040
|
lines.push(`await page.goto('/?sentient_variant=${componentId}:${forcedId}');`);
|
|
651
1041
|
lines.push("```");
|
|
652
|
-
|
|
1042
|
+
const markdown = lines.join("\n");
|
|
1043
|
+
return {
|
|
1044
|
+
content: [{ type: "text", text: markdown }],
|
|
1045
|
+
structuredContent: { componentId, forcedVariantId: forcedId, goalName, markdown }
|
|
1046
|
+
};
|
|
653
1047
|
}
|
|
654
1048
|
);
|
|
655
1049
|
}
|
|
656
1050
|
|
|
657
1051
|
// src/tools/integration-guide.ts
|
|
1052
|
+
var import_zod11 = require("zod");
|
|
658
1053
|
var GUIDE = `# SentientUI integration guide \u2014 the adaptive ladder
|
|
659
1054
|
|
|
660
1055
|
SentientUI adapts a site per visitor type (personas: buyer, researcher, deal_seeker, browser,
|
|
@@ -722,11 +1117,25 @@ Use '@sentientui/react/testing': renderWithSentient(ui, { variants, slots, perso
|
|
|
722
1117
|
deterministic outcomes so tests never depend on what the optimizer serves.
|
|
723
1118
|
`;
|
|
724
1119
|
function registerIntegrationGuideTools(server) {
|
|
725
|
-
server.
|
|
1120
|
+
server.registerTool(
|
|
726
1121
|
"get_integration_guide",
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1122
|
+
{
|
|
1123
|
+
title: "Integration guide",
|
|
1124
|
+
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.",
|
|
1125
|
+
inputSchema: {},
|
|
1126
|
+
outputSchema: {
|
|
1127
|
+
guide: import_zod11.z.string().describe("The full integration guide in Markdown")
|
|
1128
|
+
},
|
|
1129
|
+
annotations: {
|
|
1130
|
+
readOnlyHint: true,
|
|
1131
|
+
idempotentHint: true,
|
|
1132
|
+
openWorldHint: false
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
1135
|
+
async () => ({
|
|
1136
|
+
content: [{ type: "text", text: GUIDE }],
|
|
1137
|
+
structuredContent: { guide: GUIDE }
|
|
1138
|
+
})
|
|
730
1139
|
);
|
|
731
1140
|
}
|
|
732
1141
|
|