@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/index.cjs
CHANGED
|
@@ -78,14 +78,29 @@ function createProjectGuidance(err) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
function registerProjectTools(server, client) {
|
|
81
|
-
server.
|
|
81
|
+
server.registerTool(
|
|
82
82
|
"create_project",
|
|
83
|
-
"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.",
|
|
84
83
|
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
title: "Create project",
|
|
85
|
+
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.",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
name: import_zod.z.string().min(1).describe("Human-readable project name"),
|
|
88
|
+
contextType: import_zod.z.enum(["saas", "ecommerce", "marketing", "internal"]).optional().describe("What kind of product this is; defaults to saas"),
|
|
89
|
+
framework: import_zod.z.enum(["next-app", "next-pages", "react", "core"]).optional().describe("Frontend framework, used to tailor setup; defaults to next-app"),
|
|
90
|
+
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")
|
|
91
|
+
},
|
|
92
|
+
outputSchema: {
|
|
93
|
+
projectId: import_zod.z.string().describe("The new project UUID"),
|
|
94
|
+
publicKey: import_zod.z.string().describe("The pk_ public key to configure the SDK with"),
|
|
95
|
+
name: import_zod.z.string().describe("The project name"),
|
|
96
|
+
contextType: import_zod.z.string().describe("The resolved context type")
|
|
97
|
+
},
|
|
98
|
+
annotations: {
|
|
99
|
+
readOnlyHint: false,
|
|
100
|
+
destructiveHint: false,
|
|
101
|
+
idempotentHint: false,
|
|
102
|
+
openWorldHint: false
|
|
103
|
+
}
|
|
89
104
|
},
|
|
90
105
|
async ({ name, contextType, framework, websiteUrl }) => {
|
|
91
106
|
try {
|
|
@@ -95,15 +110,22 @@ function registerProjectTools(server, client) {
|
|
|
95
110
|
framework,
|
|
96
111
|
origin: websiteUrl
|
|
97
112
|
});
|
|
113
|
+
const resolvedContextType = contextType ?? "saas";
|
|
98
114
|
return {
|
|
99
115
|
content: [{
|
|
100
116
|
type: "text",
|
|
101
117
|
text: [
|
|
102
|
-
`Created project "${name}" (id: ${created.id}, type: ${
|
|
118
|
+
`Created project "${name}" (id: ${created.id}, type: ${resolvedContextType}).`,
|
|
103
119
|
`Public key: ${created.apiKey}`,
|
|
104
120
|
`Next: install @sentientui/react with this key. Ask me to pull the setup guide (get_integration_guide) and I'll wrap your first component.`
|
|
105
121
|
].join("\n")
|
|
106
|
-
}]
|
|
122
|
+
}],
|
|
123
|
+
structuredContent: {
|
|
124
|
+
projectId: created.id,
|
|
125
|
+
publicKey: created.apiKey,
|
|
126
|
+
name,
|
|
127
|
+
contextType: resolvedContextType
|
|
128
|
+
}
|
|
107
129
|
};
|
|
108
130
|
} catch (err) {
|
|
109
131
|
if (err instanceof ApiError) {
|
|
@@ -116,22 +138,65 @@ function registerProjectTools(server, client) {
|
|
|
116
138
|
}
|
|
117
139
|
}
|
|
118
140
|
);
|
|
119
|
-
server.
|
|
141
|
+
server.registerTool(
|
|
120
142
|
"list_projects",
|
|
121
|
-
|
|
122
|
-
|
|
143
|
+
{
|
|
144
|
+
title: "List projects",
|
|
145
|
+
description: "List all SentientUI projects for the authenticated account.",
|
|
146
|
+
inputSchema: {},
|
|
147
|
+
outputSchema: {
|
|
148
|
+
projects: import_zod.z.array(
|
|
149
|
+
import_zod.z.object({
|
|
150
|
+
id: import_zod.z.string().describe("Project UUID"),
|
|
151
|
+
name: import_zod.z.string(),
|
|
152
|
+
contextType: import_zod.z.string(),
|
|
153
|
+
createdAt: import_zod.z.string().describe("ISO date (YYYY-MM-DD)")
|
|
154
|
+
})
|
|
155
|
+
).describe("All projects for the account (empty if none)")
|
|
156
|
+
},
|
|
157
|
+
annotations: {
|
|
158
|
+
readOnlyHint: true,
|
|
159
|
+
idempotentHint: true,
|
|
160
|
+
openWorldHint: false
|
|
161
|
+
}
|
|
162
|
+
},
|
|
123
163
|
async () => {
|
|
124
164
|
const projects = await client.get("/projects");
|
|
125
165
|
const text = projects.length === 0 ? "No projects found." : projects.map(
|
|
126
166
|
(p) => `- ${p.name} (id: ${p.id}, type: ${p.context_type}, created: ${p.created_at.slice(0, 10)})`
|
|
127
167
|
).join("\n");
|
|
128
|
-
return {
|
|
168
|
+
return {
|
|
169
|
+
content: [{ type: "text", text }],
|
|
170
|
+
structuredContent: {
|
|
171
|
+
projects: projects.map((p) => ({
|
|
172
|
+
id: p.id,
|
|
173
|
+
name: p.name,
|
|
174
|
+
contextType: p.context_type,
|
|
175
|
+
createdAt: p.created_at.slice(0, 10)
|
|
176
|
+
}))
|
|
177
|
+
}
|
|
178
|
+
};
|
|
129
179
|
}
|
|
130
180
|
);
|
|
131
|
-
server.
|
|
181
|
+
server.registerTool(
|
|
132
182
|
"get_project_stats",
|
|
133
|
-
|
|
134
|
-
|
|
183
|
+
{
|
|
184
|
+
title: "Project health stats",
|
|
185
|
+
description: "Get health stats for a project: event volume, session count, agent calls, and status.",
|
|
186
|
+
inputSchema: { projectId: projectIdSchema },
|
|
187
|
+
outputSchema: {
|
|
188
|
+
status: import_zod.z.string().describe("Overall project health status"),
|
|
189
|
+
events24h: import_zod.z.number().describe("Events in the last 24 hours"),
|
|
190
|
+
sessions24h: import_zod.z.number().describe("Sessions in the last 24 hours"),
|
|
191
|
+
agentCalls: import_zod.z.number().describe("Total agent (MCP/API) calls"),
|
|
192
|
+
lastEventAt: import_zod.z.string().nullable().describe("ISO timestamp of the last event, or null")
|
|
193
|
+
},
|
|
194
|
+
annotations: {
|
|
195
|
+
readOnlyHint: true,
|
|
196
|
+
idempotentHint: true,
|
|
197
|
+
openWorldHint: false
|
|
198
|
+
}
|
|
199
|
+
},
|
|
135
200
|
async ({ projectId }) => {
|
|
136
201
|
const id = encodeURIComponent(projectId);
|
|
137
202
|
const stats = await client.get(`/projects/${id}/health`);
|
|
@@ -142,7 +207,16 @@ function registerProjectTools(server, client) {
|
|
|
142
207
|
`Agent calls (total): ${stats.agentCalls}`,
|
|
143
208
|
`Last event: ${stats.lastEventAt ?? "never"}`
|
|
144
209
|
].join("\n");
|
|
145
|
-
return {
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: "text", text }],
|
|
212
|
+
structuredContent: {
|
|
213
|
+
status: stats.status,
|
|
214
|
+
events24h: stats.events24h,
|
|
215
|
+
sessions24h: stats.sessions24h,
|
|
216
|
+
agentCalls: stats.agentCalls,
|
|
217
|
+
lastEventAt: stats.lastEventAt
|
|
218
|
+
}
|
|
219
|
+
};
|
|
146
220
|
}
|
|
147
221
|
);
|
|
148
222
|
}
|
|
@@ -151,37 +225,97 @@ function registerProjectTools(server, client) {
|
|
|
151
225
|
var import_zod2 = require("zod");
|
|
152
226
|
var projectIdSchema2 = import_zod2.z.string().uuid().describe("The project UUID");
|
|
153
227
|
function registerComponentTools(server, client) {
|
|
154
|
-
server.
|
|
228
|
+
server.registerTool(
|
|
155
229
|
"list_components",
|
|
156
|
-
|
|
157
|
-
|
|
230
|
+
{
|
|
231
|
+
title: "List components",
|
|
232
|
+
description: "List all adaptive components in a project with variant counts and impression totals.",
|
|
233
|
+
inputSchema: { projectId: projectIdSchema2 },
|
|
234
|
+
outputSchema: {
|
|
235
|
+
components: import_zod2.z.array(
|
|
236
|
+
import_zod2.z.object({
|
|
237
|
+
componentId: import_zod2.z.string(),
|
|
238
|
+
variantCount: import_zod2.z.number(),
|
|
239
|
+
impressions: import_zod2.z.number(),
|
|
240
|
+
conversions: import_zod2.z.number()
|
|
241
|
+
})
|
|
242
|
+
).describe("Adaptive components in the project (empty if none)")
|
|
243
|
+
},
|
|
244
|
+
annotations: {
|
|
245
|
+
readOnlyHint: true,
|
|
246
|
+
idempotentHint: true,
|
|
247
|
+
openWorldHint: false
|
|
248
|
+
}
|
|
249
|
+
},
|
|
158
250
|
async ({ projectId }) => {
|
|
159
251
|
const id = encodeURIComponent(projectId);
|
|
160
252
|
const components = await client.get(`/projects/${id}/components`);
|
|
253
|
+
const structuredContent = {
|
|
254
|
+
components: components.map((c) => ({
|
|
255
|
+
componentId: c.component_id,
|
|
256
|
+
variantCount: c.variants.length,
|
|
257
|
+
impressions: c.total_impressions,
|
|
258
|
+
conversions: c.total_conversions
|
|
259
|
+
}))
|
|
260
|
+
};
|
|
161
261
|
if (!components.length) {
|
|
162
|
-
return {
|
|
262
|
+
return {
|
|
263
|
+
content: [{ type: "text", text: "No components found for this project." }],
|
|
264
|
+
structuredContent
|
|
265
|
+
};
|
|
163
266
|
}
|
|
164
267
|
const text = components.map(
|
|
165
268
|
(c) => `- ${c.component_id}: ${c.variants.length} variants, ${c.total_impressions} impressions, ${c.total_conversions} conversions`
|
|
166
269
|
).join("\n");
|
|
167
|
-
return { content: [{ type: "text", text }] };
|
|
270
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
168
271
|
}
|
|
169
272
|
);
|
|
170
|
-
server.
|
|
273
|
+
server.registerTool(
|
|
171
274
|
"get_variant_performance",
|
|
172
|
-
|
|
173
|
-
|
|
275
|
+
{
|
|
276
|
+
title: "Variant performance",
|
|
277
|
+
description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
|
|
278
|
+
inputSchema: { projectId: projectIdSchema2 },
|
|
279
|
+
outputSchema: {
|
|
280
|
+
variants: import_zod2.z.array(
|
|
281
|
+
import_zod2.z.object({
|
|
282
|
+
variantId: import_zod2.z.string(),
|
|
283
|
+
currentCvr: import_zod2.z.number().describe("Conversion rate over the last 7 days (0-1)"),
|
|
284
|
+
priorCvr: import_zod2.z.number().describe("Conversion rate over the prior 7 days (0-1)"),
|
|
285
|
+
deltaPp: import_zod2.z.number().describe("Change in percentage points"),
|
|
286
|
+
momentum: import_zod2.z.string().describe("Momentum direction: gaining, losing, or stable")
|
|
287
|
+
})
|
|
288
|
+
).describe("Per-variant performance (empty if no data yet)")
|
|
289
|
+
},
|
|
290
|
+
annotations: {
|
|
291
|
+
readOnlyHint: true,
|
|
292
|
+
idempotentHint: true,
|
|
293
|
+
openWorldHint: false
|
|
294
|
+
}
|
|
295
|
+
},
|
|
174
296
|
async ({ projectId }) => {
|
|
175
297
|
const id = encodeURIComponent(projectId);
|
|
176
298
|
const data = await client.get(`/projects/${id}/trends`);
|
|
299
|
+
const momentumMap = new Map((data.momentum ?? []).map((m) => [m.variantId, m.direction]));
|
|
300
|
+
const structuredContent = {
|
|
301
|
+
variants: (data.cvr ?? []).map((v) => ({
|
|
302
|
+
variantId: v.variantId,
|
|
303
|
+
currentCvr: v.currentCvr,
|
|
304
|
+
priorCvr: v.priorCvr,
|
|
305
|
+
deltaPp: v.deltaPp,
|
|
306
|
+
momentum: momentumMap.get(v.variantId) ?? "stable"
|
|
307
|
+
}))
|
|
308
|
+
};
|
|
177
309
|
if (!data.cvr?.length) {
|
|
178
|
-
return {
|
|
310
|
+
return {
|
|
311
|
+
content: [{ type: "text", text: "No variant data available yet." }],
|
|
312
|
+
structuredContent
|
|
313
|
+
};
|
|
179
314
|
}
|
|
180
|
-
const momentumMap = new Map(data.momentum.map((m) => [m.variantId, m.direction]));
|
|
181
315
|
const text = data.cvr.map(
|
|
182
316
|
(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"})`
|
|
183
317
|
).join("\n");
|
|
184
|
-
return { content: [{ type: "text", text }] };
|
|
318
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
185
319
|
}
|
|
186
320
|
);
|
|
187
321
|
}
|
|
@@ -190,28 +324,63 @@ function registerComponentTools(server, client) {
|
|
|
190
324
|
var import_zod3 = require("zod");
|
|
191
325
|
var projectIdSchema3 = import_zod3.z.string().uuid().describe("The project UUID");
|
|
192
326
|
function registerInsightTools(server, client) {
|
|
193
|
-
server.
|
|
327
|
+
server.registerTool(
|
|
194
328
|
"get_insights",
|
|
195
|
-
|
|
196
|
-
|
|
329
|
+
{
|
|
330
|
+
title: "Get insights",
|
|
331
|
+
description: "Get the latest AI-generated insights: narrator observations and (Growth tier) advisor recommendations.",
|
|
332
|
+
inputSchema: { projectId: projectIdSchema3 },
|
|
333
|
+
outputSchema: {
|
|
334
|
+
status: import_zod3.z.enum(["ok", "empty"]).describe("Whether insights exist yet"),
|
|
335
|
+
observations: import_zod3.z.array(import_zod3.z.string()).describe("Narrator observations"),
|
|
336
|
+
recommendations: import_zod3.z.array(import_zod3.z.string()).describe("Advisor recommendations (Growth tier)"),
|
|
337
|
+
isStale: import_zod3.z.boolean().describe("True when the insights are older than ~6h"),
|
|
338
|
+
generatedAt: import_zod3.z.string().nullable().describe("ISO timestamp the insights were generated, or null")
|
|
339
|
+
},
|
|
340
|
+
annotations: {
|
|
341
|
+
readOnlyHint: true,
|
|
342
|
+
idempotentHint: true,
|
|
343
|
+
openWorldHint: false
|
|
344
|
+
}
|
|
345
|
+
},
|
|
197
346
|
async ({ projectId }) => {
|
|
198
347
|
const id = encodeURIComponent(projectId);
|
|
199
348
|
const data = await client.get(`/projects/${id}/insights`);
|
|
200
349
|
if (data.status === "empty") {
|
|
201
|
-
return {
|
|
350
|
+
return {
|
|
351
|
+
content: [{ type: "text", text: "No insights generated yet. Use refresh_insights to generate." }],
|
|
352
|
+
structuredContent: {
|
|
353
|
+
status: "empty",
|
|
354
|
+
observations: [],
|
|
355
|
+
recommendations: [],
|
|
356
|
+
isStale: false,
|
|
357
|
+
generatedAt: null
|
|
358
|
+
}
|
|
359
|
+
};
|
|
202
360
|
}
|
|
361
|
+
const observations = data.narratorBullets ?? [];
|
|
362
|
+
const recommendations = data.advisorBullets ?? [];
|
|
203
363
|
const lines = [];
|
|
204
364
|
if (data.isStale) lines.push("\u26A0 Insights are stale (>6h old). Consider calling refresh_insights.");
|
|
205
365
|
if (data.generatedAt) lines.push(`Generated: ${new Date(data.generatedAt).toUTCString()}`);
|
|
206
366
|
lines.push("");
|
|
207
367
|
lines.push("Observations:");
|
|
208
|
-
|
|
209
|
-
if (
|
|
368
|
+
observations.forEach((b) => lines.push(`- ${b}`));
|
|
369
|
+
if (recommendations.length) {
|
|
210
370
|
lines.push("");
|
|
211
371
|
lines.push("Recommendations:");
|
|
212
|
-
|
|
372
|
+
recommendations.forEach((b) => lines.push(`- ${b}`));
|
|
213
373
|
}
|
|
214
|
-
return {
|
|
374
|
+
return {
|
|
375
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
376
|
+
structuredContent: {
|
|
377
|
+
status: "ok",
|
|
378
|
+
observations,
|
|
379
|
+
recommendations,
|
|
380
|
+
isStale: data.isStale ?? false,
|
|
381
|
+
generatedAt: data.generatedAt ?? null
|
|
382
|
+
}
|
|
383
|
+
};
|
|
215
384
|
}
|
|
216
385
|
);
|
|
217
386
|
}
|
|
@@ -220,15 +389,46 @@ function registerInsightTools(server, client) {
|
|
|
220
389
|
var import_zod4 = require("zod");
|
|
221
390
|
var projectIdSchema4 = import_zod4.z.string().uuid().describe("The project UUID");
|
|
222
391
|
function registerPersonaTools(server, client) {
|
|
223
|
-
server.
|
|
392
|
+
server.registerTool(
|
|
224
393
|
"get_persona_breakdown",
|
|
225
|
-
|
|
226
|
-
|
|
394
|
+
{
|
|
395
|
+
title: "Persona breakdown",
|
|
396
|
+
description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
|
|
397
|
+
inputSchema: { projectId: projectIdSchema4 },
|
|
398
|
+
outputSchema: {
|
|
399
|
+
totalSessions: import_zod4.z.number().describe("Total sessions across all clusters"),
|
|
400
|
+
clusters: import_zod4.z.array(
|
|
401
|
+
import_zod4.z.object({
|
|
402
|
+
label: import_zod4.z.string(),
|
|
403
|
+
sessionCount: import_zod4.z.number(),
|
|
404
|
+
sharePct: import_zod4.z.number().describe("Share of total traffic (0-100)"),
|
|
405
|
+
reliability: import_zod4.z.number().describe("Average cluster reliability (0-1)")
|
|
406
|
+
})
|
|
407
|
+
).describe("Persona clusters (empty until enough visitor data)")
|
|
408
|
+
},
|
|
409
|
+
annotations: {
|
|
410
|
+
readOnlyHint: true,
|
|
411
|
+
idempotentHint: true,
|
|
412
|
+
openWorldHint: false
|
|
413
|
+
}
|
|
414
|
+
},
|
|
227
415
|
async ({ projectId }) => {
|
|
228
416
|
const id = encodeURIComponent(projectId);
|
|
229
417
|
const data = await client.get(`/projects/${id}/portraits`);
|
|
418
|
+
const structuredContent = {
|
|
419
|
+
totalSessions: data.totalSessions,
|
|
420
|
+
clusters: data.clusters.map((c) => ({
|
|
421
|
+
label: c.label,
|
|
422
|
+
sessionCount: c.sessionCount,
|
|
423
|
+
sharePct: data.totalSessions > 0 ? c.sessionCount / data.totalSessions * 100 : 0,
|
|
424
|
+
reliability: c.avgReliability
|
|
425
|
+
}))
|
|
426
|
+
};
|
|
230
427
|
if (!data.clusters.length) {
|
|
231
|
-
return {
|
|
428
|
+
return {
|
|
429
|
+
content: [{ type: "text", text: "No persona clusters yet. More visitor data is needed." }],
|
|
430
|
+
structuredContent
|
|
431
|
+
};
|
|
232
432
|
}
|
|
233
433
|
const lines = [
|
|
234
434
|
`Total sessions: ${data.totalSessions}`,
|
|
@@ -239,7 +439,7 @@ function registerPersonaTools(server, client) {
|
|
|
239
439
|
return `- ${c.label}: ${c.sessionCount} sessions (${pct.toFixed(1)}% of traffic, reliability ${(c.avgReliability * 100).toFixed(0)}%)`;
|
|
240
440
|
})
|
|
241
441
|
];
|
|
242
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
442
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
243
443
|
}
|
|
244
444
|
);
|
|
245
445
|
}
|
|
@@ -248,22 +448,63 @@ function registerPersonaTools(server, client) {
|
|
|
248
448
|
var import_zod5 = require("zod");
|
|
249
449
|
var projectIdSchema5 = import_zod5.z.string().uuid().describe("The project UUID");
|
|
250
450
|
function registerGoalTools(server, client) {
|
|
251
|
-
server.
|
|
451
|
+
server.registerTool(
|
|
252
452
|
"get_goal_funnel",
|
|
253
|
-
|
|
254
|
-
|
|
453
|
+
{
|
|
454
|
+
title: "Goal funnel",
|
|
455
|
+
description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
|
|
456
|
+
inputSchema: { projectId: projectIdSchema5 },
|
|
457
|
+
outputSchema: {
|
|
458
|
+
goals: import_zod5.z.array(
|
|
459
|
+
import_zod5.z.object({
|
|
460
|
+
goalName: import_zod5.z.string(),
|
|
461
|
+
hits: import_zod5.z.number(),
|
|
462
|
+
uniqueSessions: import_zod5.z.number(),
|
|
463
|
+
conversionRate: import_zod5.z.number().describe("Unique-session conversion rate (0-1)"),
|
|
464
|
+
variants: import_zod5.z.array(
|
|
465
|
+
import_zod5.z.object({
|
|
466
|
+
componentId: import_zod5.z.string(),
|
|
467
|
+
variantId: import_zod5.z.string(),
|
|
468
|
+
completionRate: import_zod5.z.number().describe("Completion rate per assigned session (0-1)")
|
|
469
|
+
})
|
|
470
|
+
).describe("Per-variant breakdown")
|
|
471
|
+
})
|
|
472
|
+
).describe("Configured goals (empty if none)")
|
|
473
|
+
},
|
|
474
|
+
annotations: {
|
|
475
|
+
readOnlyHint: true,
|
|
476
|
+
idempotentHint: true,
|
|
477
|
+
openWorldHint: false
|
|
478
|
+
}
|
|
479
|
+
},
|
|
255
480
|
async ({ projectId }) => {
|
|
256
481
|
const id = encodeURIComponent(projectId);
|
|
257
482
|
const data = await client.get(`/projects/${id}/goals`);
|
|
483
|
+
const structuredContent = {
|
|
484
|
+
goals: data.goals.map((g) => ({
|
|
485
|
+
goalName: g.goalName,
|
|
486
|
+
hits: g.hits,
|
|
487
|
+
uniqueSessions: g.uniqueSessions,
|
|
488
|
+
conversionRate: g.pct,
|
|
489
|
+
variants: g.variants.map((v) => ({
|
|
490
|
+
componentId: v.componentId,
|
|
491
|
+
variantId: v.variantId,
|
|
492
|
+
completionRate: v.completionRate
|
|
493
|
+
}))
|
|
494
|
+
}))
|
|
495
|
+
};
|
|
258
496
|
if (!data.goals.length) {
|
|
259
|
-
return {
|
|
497
|
+
return {
|
|
498
|
+
content: [{ type: "text", text: "No goals configured for this project." }],
|
|
499
|
+
structuredContent
|
|
500
|
+
};
|
|
260
501
|
}
|
|
261
502
|
const lines = data.goals.flatMap((g) => [
|
|
262
503
|
`${g.goalName}: ${g.hits} hits, ${g.uniqueSessions} unique sessions, ${(g.pct * 100).toFixed(1)}% conversion`,
|
|
263
504
|
...g.variants.map((v) => ` ${v.componentId}/${v.variantId}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
|
|
264
505
|
""
|
|
265
506
|
]);
|
|
266
|
-
return { content: [{ type: "text", text: lines.join("\n").trim() }] };
|
|
507
|
+
return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
|
|
267
508
|
}
|
|
268
509
|
);
|
|
269
510
|
}
|
|
@@ -272,20 +513,47 @@ function registerGoalTools(server, client) {
|
|
|
272
513
|
var import_zod6 = require("zod");
|
|
273
514
|
var projectIdSchema6 = import_zod6.z.string().uuid().describe("The project UUID");
|
|
274
515
|
function registerGuardrailTools(server, client) {
|
|
275
|
-
server.
|
|
516
|
+
server.registerTool(
|
|
276
517
|
"list_guardrail_events",
|
|
277
|
-
|
|
278
|
-
|
|
518
|
+
{
|
|
519
|
+
title: "List guardrail events",
|
|
520
|
+
description: "List variants currently paused by the guardrail in the last 24 hours.",
|
|
521
|
+
inputSchema: { projectId: projectIdSchema6 },
|
|
522
|
+
outputSchema: {
|
|
523
|
+
events: import_zod6.z.array(
|
|
524
|
+
import_zod6.z.object({
|
|
525
|
+
componentId: import_zod6.z.string(),
|
|
526
|
+
variantIds: import_zod6.z.array(import_zod6.z.string()).describe("Variants paused by the guardrail"),
|
|
527
|
+
pausedAt: import_zod6.z.string().nullable().describe("ISO timestamp the pause fired, or null")
|
|
528
|
+
})
|
|
529
|
+
).describe("Guardrail events in the last 24h (empty if none)")
|
|
530
|
+
},
|
|
531
|
+
annotations: {
|
|
532
|
+
readOnlyHint: true,
|
|
533
|
+
idempotentHint: true,
|
|
534
|
+
openWorldHint: false
|
|
535
|
+
}
|
|
536
|
+
},
|
|
279
537
|
async ({ projectId }) => {
|
|
280
538
|
const id = encodeURIComponent(projectId);
|
|
281
539
|
const data = await client.get(`/projects/${id}/guardrail-events`);
|
|
540
|
+
const structuredContent = {
|
|
541
|
+
events: data.guardrailEvents.map((e) => ({
|
|
542
|
+
componentId: e.componentId,
|
|
543
|
+
variantIds: e.variantIds,
|
|
544
|
+
pausedAt: e.pausedAt
|
|
545
|
+
}))
|
|
546
|
+
};
|
|
282
547
|
if (!data.guardrailEvents.length) {
|
|
283
|
-
return {
|
|
548
|
+
return {
|
|
549
|
+
content: [{ type: "text", text: "No active guardrail events in the last 24 hours." }],
|
|
550
|
+
structuredContent
|
|
551
|
+
};
|
|
284
552
|
}
|
|
285
553
|
const lines = data.guardrailEvents.map(
|
|
286
554
|
(e) => `- ${e.componentId}: variants [${e.variantIds.join(", ")}] paused${e.pausedAt ? ` at ${e.pausedAt}` : ""}`
|
|
287
555
|
);
|
|
288
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
556
|
+
return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
|
|
289
557
|
}
|
|
290
558
|
);
|
|
291
559
|
}
|
|
@@ -294,20 +562,49 @@ function registerGuardrailTools(server, client) {
|
|
|
294
562
|
var import_zod7 = require("zod");
|
|
295
563
|
var projectIdSchema7 = import_zod7.z.string().uuid().describe("The project UUID");
|
|
296
564
|
function registerLayoutTools(server, client) {
|
|
297
|
-
server.
|
|
565
|
+
server.registerTool(
|
|
298
566
|
"get_layout_stats",
|
|
299
|
-
|
|
300
|
-
|
|
567
|
+
{
|
|
568
|
+
title: "Layout stats",
|
|
569
|
+
description: "Get per-persona section layout rankings and bandit reward weights.",
|
|
570
|
+
inputSchema: { projectId: projectIdSchema7 },
|
|
571
|
+
outputSchema: {
|
|
572
|
+
layouts: import_zod7.z.array(
|
|
573
|
+
import_zod7.z.object({
|
|
574
|
+
persona: import_zod7.z.string(),
|
|
575
|
+
layoutOrder: import_zod7.z.array(import_zod7.z.string()).describe("Ranked section order for this persona"),
|
|
576
|
+
pulls: import_zod7.z.number().describe("Number of times this arm was served"),
|
|
577
|
+
avgReward: import_zod7.z.number().describe("Average bandit reward weight")
|
|
578
|
+
})
|
|
579
|
+
).describe("Per-persona layout rankings (empty until enough sessions)")
|
|
580
|
+
},
|
|
581
|
+
annotations: {
|
|
582
|
+
readOnlyHint: true,
|
|
583
|
+
idempotentHint: true,
|
|
584
|
+
openWorldHint: false
|
|
585
|
+
}
|
|
586
|
+
},
|
|
301
587
|
async ({ projectId }) => {
|
|
302
588
|
const id = encodeURIComponent(projectId);
|
|
303
589
|
const stats = await client.get(`/projects/${id}/layout-stats`);
|
|
590
|
+
const structuredContent = {
|
|
591
|
+
layouts: stats.map((s) => ({
|
|
592
|
+
persona: s.persona,
|
|
593
|
+
layoutOrder: s.layoutOrder,
|
|
594
|
+
pulls: s.pulls,
|
|
595
|
+
avgReward: s.avgReward
|
|
596
|
+
}))
|
|
597
|
+
};
|
|
304
598
|
if (!stats.length) {
|
|
305
|
-
return {
|
|
599
|
+
return {
|
|
600
|
+
content: [{ type: "text", text: "No layout data yet. More visitor sessions are needed." }],
|
|
601
|
+
structuredContent
|
|
602
|
+
};
|
|
306
603
|
}
|
|
307
604
|
const text = stats.map(
|
|
308
605
|
(s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
|
|
309
606
|
).join("\n");
|
|
310
|
-
return { content: [{ type: "text", text }] };
|
|
607
|
+
return { content: [{ type: "text", text }], structuredContent };
|
|
311
608
|
}
|
|
312
609
|
);
|
|
313
610
|
}
|
|
@@ -316,14 +613,30 @@ function registerLayoutTools(server, client) {
|
|
|
316
613
|
var import_zod8 = require("zod");
|
|
317
614
|
var projectIdSchema8 = import_zod8.z.string().uuid().describe("The project UUID");
|
|
318
615
|
function registerVariantWriteTools(server, client) {
|
|
319
|
-
server.
|
|
616
|
+
server.registerTool(
|
|
320
617
|
"create_variant",
|
|
321
|
-
"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).",
|
|
322
618
|
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
619
|
+
title: "Create managed variant",
|
|
620
|
+
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).",
|
|
621
|
+
inputSchema: {
|
|
622
|
+
projectId: projectIdSchema8,
|
|
623
|
+
componentId: import_zod8.z.string().describe("The component ID to add a variant to"),
|
|
624
|
+
displayName: import_zod8.z.string().describe("Human-readable name for the new variant"),
|
|
625
|
+
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.")
|
|
626
|
+
},
|
|
627
|
+
outputSchema: {
|
|
628
|
+
variantId: import_zod8.z.string().describe("The new variant ID"),
|
|
629
|
+
displayName: import_zod8.z.string(),
|
|
630
|
+
componentId: import_zod8.z.string(),
|
|
631
|
+
state: import_zod8.z.literal("draft").describe("New managed variants start in draft state"),
|
|
632
|
+
hasContent: import_zod8.z.boolean().describe("Whether text content was provided at creation")
|
|
633
|
+
},
|
|
634
|
+
annotations: {
|
|
635
|
+
readOnlyHint: false,
|
|
636
|
+
destructiveHint: false,
|
|
637
|
+
idempotentHint: false,
|
|
638
|
+
openWorldHint: false
|
|
639
|
+
}
|
|
327
640
|
},
|
|
328
641
|
async ({ projectId, componentId, displayName, content }) => {
|
|
329
642
|
const id = encodeURIComponent(projectId);
|
|
@@ -336,17 +649,38 @@ function registerVariantWriteTools(server, client) {
|
|
|
336
649
|
content: [{
|
|
337
650
|
type: "text",
|
|
338
651
|
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).`
|
|
339
|
-
}]
|
|
652
|
+
}],
|
|
653
|
+
structuredContent: {
|
|
654
|
+
variantId: result.variantId,
|
|
655
|
+
displayName: result.displayName,
|
|
656
|
+
componentId,
|
|
657
|
+
state: "draft",
|
|
658
|
+
hasContent: Boolean(content)
|
|
659
|
+
}
|
|
340
660
|
};
|
|
341
661
|
}
|
|
342
662
|
);
|
|
343
|
-
server.
|
|
663
|
+
server.registerTool(
|
|
344
664
|
"pause_variant",
|
|
345
|
-
"Pause a variant, stopping traffic from being assigned to it.",
|
|
346
665
|
{
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
666
|
+
title: "Pause variant",
|
|
667
|
+
description: "Pause a variant, stopping traffic from being assigned to it.",
|
|
668
|
+
inputSchema: {
|
|
669
|
+
projectId: projectIdSchema8,
|
|
670
|
+
componentId: import_zod8.z.string().describe("The component ID"),
|
|
671
|
+
variantId: import_zod8.z.string().describe("The variant ID to pause")
|
|
672
|
+
},
|
|
673
|
+
outputSchema: {
|
|
674
|
+
variantId: import_zod8.z.string(),
|
|
675
|
+
componentId: import_zod8.z.string(),
|
|
676
|
+
paused: import_zod8.z.literal(true).describe("The variant is now paused")
|
|
677
|
+
},
|
|
678
|
+
annotations: {
|
|
679
|
+
readOnlyHint: false,
|
|
680
|
+
destructiveHint: false,
|
|
681
|
+
idempotentHint: true,
|
|
682
|
+
openWorldHint: false
|
|
683
|
+
}
|
|
350
684
|
},
|
|
351
685
|
async ({ projectId, componentId, variantId }) => {
|
|
352
686
|
const id = encodeURIComponent(projectId);
|
|
@@ -355,14 +689,28 @@ function registerVariantWriteTools(server, client) {
|
|
|
355
689
|
content: [{
|
|
356
690
|
type: "text",
|
|
357
691
|
text: `Variant ${variantId} in component ${componentId} has been paused. No new traffic will be assigned to it.`
|
|
358
|
-
}]
|
|
692
|
+
}],
|
|
693
|
+
structuredContent: { variantId, componentId, paused: true }
|
|
359
694
|
};
|
|
360
695
|
}
|
|
361
696
|
);
|
|
362
|
-
server.
|
|
697
|
+
server.registerTool(
|
|
363
698
|
"refresh_insights",
|
|
364
|
-
|
|
365
|
-
|
|
699
|
+
{
|
|
700
|
+
title: "Refresh insights",
|
|
701
|
+
description: "Trigger fresh AI insight generation for a project. Returns immediately; use get_insights in ~15 seconds to see results.",
|
|
702
|
+
inputSchema: { projectId: projectIdSchema8 },
|
|
703
|
+
outputSchema: {
|
|
704
|
+
projectId: import_zod8.z.string(),
|
|
705
|
+
status: import_zod8.z.literal("generating").describe("Generation has been triggered")
|
|
706
|
+
},
|
|
707
|
+
annotations: {
|
|
708
|
+
readOnlyHint: false,
|
|
709
|
+
destructiveHint: false,
|
|
710
|
+
idempotentHint: false,
|
|
711
|
+
openWorldHint: false
|
|
712
|
+
}
|
|
713
|
+
},
|
|
366
714
|
async ({ projectId }) => {
|
|
367
715
|
const id = encodeURIComponent(projectId);
|
|
368
716
|
await client.post(`/projects/${id}/insights/refresh`);
|
|
@@ -370,7 +718,8 @@ function registerVariantWriteTools(server, client) {
|
|
|
370
718
|
content: [{
|
|
371
719
|
type: "text",
|
|
372
720
|
text: `Insights are generating for project ${projectId}. Call get_insights in ~15 seconds to see the results.`
|
|
373
|
-
}]
|
|
721
|
+
}],
|
|
722
|
+
structuredContent: { projectId, status: "generating" }
|
|
374
723
|
};
|
|
375
724
|
}
|
|
376
725
|
);
|
|
@@ -443,12 +792,28 @@ async function settled(p) {
|
|
|
443
792
|
}
|
|
444
793
|
}
|
|
445
794
|
function registerVariantBriefTools(server, client) {
|
|
446
|
-
server.
|
|
795
|
+
server.registerTool(
|
|
447
796
|
"get_variant_brief",
|
|
448
|
-
"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.",
|
|
449
797
|
{
|
|
450
|
-
|
|
451
|
-
|
|
798
|
+
title: "Variant brief",
|
|
799
|
+
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.",
|
|
800
|
+
inputSchema: {
|
|
801
|
+
projectId: projectIdSchema9,
|
|
802
|
+
componentId: import_zod9.z.string().describe('The component ID to write a new variant for (matches <Adaptive id="...">).')
|
|
803
|
+
},
|
|
804
|
+
outputSchema: {
|
|
805
|
+
componentId: import_zod9.z.string(),
|
|
806
|
+
contextType: import_zod9.z.string().describe("The project's context type (or 'unknown')"),
|
|
807
|
+
dataState: import_zod9.z.enum(["sufficient", "collecting", "empty"]).describe("Data-sufficiency assessment"),
|
|
808
|
+
existingVariantIds: import_zod9.z.array(import_zod9.z.string()).describe("Variant IDs already in use (do not reuse)"),
|
|
809
|
+
priors: import_zod9.z.array(import_zod9.z.string()).describe("Best-practice priors applied for this context type"),
|
|
810
|
+
markdown: import_zod9.z.string().describe("The full variant brief in Markdown")
|
|
811
|
+
},
|
|
812
|
+
annotations: {
|
|
813
|
+
readOnlyHint: true,
|
|
814
|
+
idempotentHint: true,
|
|
815
|
+
openWorldHint: false
|
|
816
|
+
}
|
|
452
817
|
},
|
|
453
818
|
async ({ projectId, componentId }) => {
|
|
454
819
|
const id = encodeURIComponent(projectId);
|
|
@@ -539,7 +904,18 @@ function registerVariantBriefTools(server, client) {
|
|
|
539
904
|
lines.push("5. Commit, push, and deploy. Optionally enable shadow mode for this component first if you want to validate before serving real traffic.");
|
|
540
905
|
lines.push("");
|
|
541
906
|
lines.push("Make the change reflect the data sufficiency above: data-driven when SUFFICIENT, best-practice-led when COLLECTING or EMPTY.");
|
|
542
|
-
|
|
907
|
+
const markdown = lines.join("\n");
|
|
908
|
+
return {
|
|
909
|
+
content: [{ type: "text", text: markdown }],
|
|
910
|
+
structuredContent: {
|
|
911
|
+
componentId,
|
|
912
|
+
contextType,
|
|
913
|
+
dataState,
|
|
914
|
+
existingVariantIds,
|
|
915
|
+
priors: priorsFor(contextType),
|
|
916
|
+
markdown
|
|
917
|
+
}
|
|
918
|
+
};
|
|
543
919
|
}
|
|
544
920
|
);
|
|
545
921
|
}
|
|
@@ -555,12 +931,26 @@ async function settled2(p) {
|
|
|
555
931
|
}
|
|
556
932
|
}
|
|
557
933
|
function registerTestBriefTools(server, client) {
|
|
558
|
-
server.
|
|
934
|
+
server.registerTool(
|
|
559
935
|
"get_test_brief",
|
|
560
|
-
"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).",
|
|
561
936
|
{
|
|
562
|
-
|
|
563
|
-
|
|
937
|
+
title: "Test brief",
|
|
938
|
+
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).",
|
|
939
|
+
inputSchema: {
|
|
940
|
+
projectId: projectIdSchema10,
|
|
941
|
+
componentId: import_zod10.z.string().describe('The component ID to write a test for (matches <Adaptive id="...">).')
|
|
942
|
+
},
|
|
943
|
+
outputSchema: {
|
|
944
|
+
componentId: import_zod10.z.string(),
|
|
945
|
+
forcedVariantId: import_zod10.z.string().describe("The non-control variant the example forces"),
|
|
946
|
+
goalName: import_zod10.z.string().describe("The goal the example asserts fires"),
|
|
947
|
+
markdown: import_zod10.z.string().describe("The full test brief in Markdown")
|
|
948
|
+
},
|
|
949
|
+
annotations: {
|
|
950
|
+
readOnlyHint: true,
|
|
951
|
+
idempotentHint: true,
|
|
952
|
+
openWorldHint: false
|
|
953
|
+
}
|
|
564
954
|
},
|
|
565
955
|
async ({ projectId, componentId }) => {
|
|
566
956
|
const id = encodeURIComponent(projectId);
|
|
@@ -624,12 +1014,17 @@ function registerTestBriefTools(server, client) {
|
|
|
624
1014
|
lines.push("```ts");
|
|
625
1015
|
lines.push(`await page.goto('/?sentient_variant=${componentId}:${forcedId}');`);
|
|
626
1016
|
lines.push("```");
|
|
627
|
-
|
|
1017
|
+
const markdown = lines.join("\n");
|
|
1018
|
+
return {
|
|
1019
|
+
content: [{ type: "text", text: markdown }],
|
|
1020
|
+
structuredContent: { componentId, forcedVariantId: forcedId, goalName, markdown }
|
|
1021
|
+
};
|
|
628
1022
|
}
|
|
629
1023
|
);
|
|
630
1024
|
}
|
|
631
1025
|
|
|
632
1026
|
// src/tools/integration-guide.ts
|
|
1027
|
+
var import_zod11 = require("zod");
|
|
633
1028
|
var GUIDE = `# SentientUI integration guide \u2014 the adaptive ladder
|
|
634
1029
|
|
|
635
1030
|
SentientUI adapts a site per visitor type (personas: buyer, researcher, deal_seeker, browser,
|
|
@@ -697,11 +1092,25 @@ Use '@sentientui/react/testing': renderWithSentient(ui, { variants, slots, perso
|
|
|
697
1092
|
deterministic outcomes so tests never depend on what the optimizer serves.
|
|
698
1093
|
`;
|
|
699
1094
|
function registerIntegrationGuideTools(server) {
|
|
700
|
-
server.
|
|
1095
|
+
server.registerTool(
|
|
701
1096
|
"get_integration_guide",
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1097
|
+
{
|
|
1098
|
+
title: "Integration guide",
|
|
1099
|
+
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.",
|
|
1100
|
+
inputSchema: {},
|
|
1101
|
+
outputSchema: {
|
|
1102
|
+
guide: import_zod11.z.string().describe("The full integration guide in Markdown")
|
|
1103
|
+
},
|
|
1104
|
+
annotations: {
|
|
1105
|
+
readOnlyHint: true,
|
|
1106
|
+
idempotentHint: true,
|
|
1107
|
+
openWorldHint: false
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
async () => ({
|
|
1111
|
+
content: [{ type: "text", text: GUIDE }],
|
|
1112
|
+
structuredContent: { guide: GUIDE }
|
|
1113
|
+
})
|
|
705
1114
|
);
|
|
706
1115
|
}
|
|
707
1116
|
|