@sentientui/mcp 0.5.2 → 0.6.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.
@@ -215,6 +215,263 @@ function registerProjectTools(server, client) {
215
215
 
216
216
  // src/tools/components.ts
217
217
  import { z as z2 } from "zod";
218
+
219
+ // src/ui/templates.ts
220
+ var VIZ_TITLES = {
221
+ "persona-breakdown": "Persona breakdown",
222
+ "variant-performance": "Variant performance",
223
+ "goal-funnel": "Goal funnel",
224
+ "layout-stats": "Layout stats"
225
+ };
226
+ var SHARED_CSS = `
227
+ :root {
228
+ --surface: #fcfcfb;
229
+ --plane: #f9f9f7;
230
+ --ink: #0b0b0b;
231
+ --ink-2: #52514e;
232
+ --muted: #898781;
233
+ --track: #ecebe6;
234
+ --border: rgba(11,11,11,0.10);
235
+ --series-1: #2a78d6;
236
+ --good: #006300;
237
+ --critical: #c0392b;
238
+ }
239
+ @media (prefers-color-scheme: dark) {
240
+ :root {
241
+ --surface: #1a1a19;
242
+ --plane: #0d0d0d;
243
+ --ink: #ffffff;
244
+ --ink-2: #c3c2b7;
245
+ --muted: #898781;
246
+ --track: #2c2c2a;
247
+ --border: rgba(255,255,255,0.10);
248
+ --series-1: #3987e5;
249
+ --good: #0ca30c;
250
+ --critical: #e06a5c;
251
+ }
252
+ }
253
+ * { box-sizing: border-box; }
254
+ html, body { margin: 0; }
255
+ body {
256
+ background: var(--surface);
257
+ color: var(--ink);
258
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
259
+ font-size: 14px;
260
+ line-height: 1.45;
261
+ padding: 16px;
262
+ }
263
+ h1 { font-size: 15px; font-weight: 650; margin: 0 0 2px; }
264
+ .sub { color: var(--ink-2); font-size: 12px; margin: 0 0 14px; }
265
+ .rows { display: flex; flex-direction: column; gap: 10px; }
266
+ .row { display: grid; grid-template-columns: 1fr; gap: 4px; }
267
+ .row-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
268
+ .label { font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
269
+ .val { color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
270
+ .track {
271
+ position: relative; height: 14px; border-radius: 4px;
272
+ background: var(--track); overflow: hidden;
273
+ }
274
+ .bar {
275
+ position: absolute; left: 0; top: 0; bottom: 0;
276
+ background: var(--series-1); border-radius: 4px; min-width: 3px;
277
+ }
278
+ .meta { color: var(--ink-2); font-size: 12px; }
279
+ .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
280
+ .chip {
281
+ border: 1px solid var(--border); border-radius: 999px;
282
+ padding: 2px 9px; font-size: 12px; color: var(--ink-2);
283
+ display: inline-flex; align-items: center; gap: 6px;
284
+ }
285
+ .chip .rank { color: var(--muted); font-variant-numeric: tabular-nums; }
286
+ .delta { font-variant-numeric: tabular-nums; font-weight: 550; white-space: nowrap; }
287
+ .delta.up { color: var(--good); }
288
+ .delta.down { color: var(--critical); }
289
+ .delta.flat { color: var(--muted); }
290
+ .empty { color: var(--ink-2); padding: 8px 0; }
291
+ .foot { color: var(--muted); font-size: 11px; margin-top: 14px; }
292
+ `;
293
+ var HELPERS_JS = `
294
+ function esc(s){ return String(s == null ? '' : s).replace(/[&<>"']/g, function(c){
295
+ return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]; }); }
296
+ function pct1(n){ return (Number(n) || 0).toFixed(1) + '%'; }
297
+ function clampPct(n){ n = Number(n) || 0; return n < 0 ? 0 : n > 100 ? 100 : n; }
298
+ function el(id){ return document.getElementById(id); }
299
+ function barRow(label, valText, widthPct, metaText){
300
+ return '<div class="row"><div class="row-head"><span class="label">' + esc(label) +
301
+ '</span><span class="val">' + esc(valText) + '</span></div>' +
302
+ '<div class="track"><span class="bar" style="width:' + clampPct(widthPct) + '%"></span></div>' +
303
+ (metaText ? '<div class="meta">' + esc(metaText) + '</div>' : '') + '</div>';
304
+ }
305
+ function showEmpty(msg){ el('viz').innerHTML = '<div class="empty">' + esc(msg) + '</div>'; }
306
+ `;
307
+ var BOOTSTRAP_JS = `
308
+ function __extract(m){
309
+ if (!m) return null;
310
+ if (m.structuredContent) return m.structuredContent;
311
+ if (m.toolOutput) return m.toolOutput;
312
+ if (m.globals && m.globals.toolOutput) return m.globals.toolOutput;
313
+ if (m.payload && m.payload.renderData) return __extract(m.payload.renderData);
314
+ if (m.renderData) return __extract(m.renderData);
315
+ return m;
316
+ }
317
+ function __boot(){
318
+ try {
319
+ var g = (window.openai && window.openai.toolOutput) || window.mcpUiRenderData ||
320
+ (window.__MCP_UI__ && window.__MCP_UI__.toolOutput);
321
+ if (g) { window.__render(g); return; }
322
+ } catch (e) {}
323
+ window.addEventListener('message', function(ev){
324
+ var data = __extract(ev && ev.data);
325
+ if (data && typeof data === 'object') { try { window.__render(data); } catch (e) {} }
326
+ });
327
+ try {
328
+ window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');
329
+ window.parent.postMessage({ type: 'openai:request_globals' }, '*');
330
+ } catch (e) {}
331
+ }
332
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', __boot);
333
+ else __boot();
334
+ `;
335
+ var RENDER_JS = {
336
+ "persona-breakdown": `
337
+ window.__render = function(data){
338
+ var clusters = (data && data.clusters) || [];
339
+ var total = (data && data.totalSessions) || 0;
340
+ el('sub').textContent = total + ' total sessions across ' + clusters.length + ' persona' + (clusters.length === 1 ? '' : 's');
341
+ if (!clusters.length) { showEmpty('No persona clusters yet \u2014 more visitor data is needed.'); return; }
342
+ var sorted = clusters.slice().sort(function(a,b){ return (b.sharePct||0) - (a.sharePct||0); });
343
+ el('viz').innerHTML = sorted.map(function(c){
344
+ var rel = Math.round((c.reliability || 0) * 100);
345
+ return barRow(c.label, pct1(c.sharePct), c.sharePct,
346
+ c.sessionCount + ' sessions \xB7 reliability ' + rel + '%');
347
+ }).join('');
348
+ };
349
+ `,
350
+ "variant-performance": `
351
+ window.__render = function(data){
352
+ var variants = (data && data.variants) || [];
353
+ el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, last 7 days vs prior 7';
354
+ if (!variants.length) { showEmpty('No variant data available yet.'); return; }
355
+ var max = variants.reduce(function(m,v){ return Math.max(m, v.currentCvr || 0); }, 0) || 1;
356
+ var sorted = variants.slice().sort(function(a,b){ return (b.currentCvr||0) - (a.currentCvr||0); });
357
+ el('viz').innerHTML = sorted.map(function(v){
358
+ var cur = (v.currentCvr || 0) * 100;
359
+ var dp = Number(v.deltaPp) || 0;
360
+ var dir = dp > 0.05 ? 'up' : dp < -0.05 ? 'down' : 'flat';
361
+ var arrow = dir === 'up' ? '\u25B2' : dir === 'down' ? '\u25BC' : '\u2014';
362
+ var sign = dp > 0 ? '+' : '';
363
+ var deltaHtml = '<span class="delta ' + dir + '">' + arrow + ' ' + sign + dp.toFixed(1) + ' pp</span>';
364
+ var head = '<div class="row-head"><span class="label">' + esc(v.variantId) +
365
+ '</span><span class="val">' + cur.toFixed(2) + '% ' + deltaHtml + '</span></div>';
366
+ return '<div class="row">' + head +
367
+ '<div class="track"><span class="bar" style="width:' + clampPct((v.currentCvr || 0) / max * 100) + '%"></span></div>' +
368
+ '<div class="meta">prior ' + ((v.priorCvr||0)*100).toFixed(2) + '% \xB7 momentum ' + esc(v.momentum || 'stable') + '</div></div>';
369
+ }).join('');
370
+ };
371
+ `,
372
+ "goal-funnel": `
373
+ window.__render = function(data){
374
+ var goals = (data && data.goals) || [];
375
+ el('sub').textContent = goals.length + ' goal' + (goals.length === 1 ? '' : 's') + ' \xB7 unique-session conversion';
376
+ if (!goals.length) { showEmpty('No goals configured for this project.'); return; }
377
+ el('viz').innerHTML = goals.map(function(g){
378
+ var cr = (g.conversionRate || 0) * 100;
379
+ var best = (g.variants || []).slice().sort(function(a,b){ return (b.completionRate||0)-(a.completionRate||0); })[0];
380
+ var meta = g.hits + ' hits \xB7 ' + g.uniqueSessions + ' unique sessions' +
381
+ (best ? ' \xB7 best: ' + esc(best.componentId + '/' + best.variantId) + ' ' + ((best.completionRate||0)*100).toFixed(1) + '%' : '');
382
+ return barRow(g.goalName, pct1(cr), cr, meta);
383
+ }).join('');
384
+ };
385
+ `,
386
+ "layout-stats": `
387
+ window.__render = function(data){
388
+ var layouts = (data && data.layouts) || [];
389
+ el('sub').textContent = layouts.length + ' persona' + (layouts.length === 1 ? '' : 's') + ' \xB7 section order by reward';
390
+ if (!layouts.length) { showEmpty('No layout data yet \u2014 more visitor sessions are needed.'); return; }
391
+ var max = layouts.reduce(function(m,l){ return Math.max(m, l.avgReward || 0); }, 0) || 1;
392
+ var sorted = layouts.slice().sort(function(a,b){ return (b.avgReward||0)-(a.avgReward||0); });
393
+ el('viz').innerHTML = sorted.map(function(l){
394
+ var order = (l.layoutOrder || []).map(function(s, i){
395
+ return '<span class="chip"><span class="rank">' + (i+1) + '</span>' + esc(s) + '</span>';
396
+ }).join('');
397
+ var head = '<div class="row-head"><span class="label">' + esc(l.persona) +
398
+ '</span><span class="val">reward ' + (Number(l.avgReward)||0).toFixed(2) + '</span></div>';
399
+ return '<div class="row">' + head +
400
+ '<div class="track"><span class="bar" style="width:' + clampPct((l.avgReward||0)/max*100) + '%"></span></div>' +
401
+ '<div class="chips">' + order + '</div>' +
402
+ '<div class="meta">' + (l.pulls || 0) + ' pulls</div></div>';
403
+ }).join('');
404
+ };
405
+ `
406
+ };
407
+ function buildTemplate(id) {
408
+ const title = VIZ_TITLES[id];
409
+ return `<!doctype html>
410
+ <html lang="en">
411
+ <head>
412
+ <meta charset="utf-8">
413
+ <meta name="viewport" content="width=device-width, initial-scale=1">
414
+ <title>${title} \u2014 SentientUI</title>
415
+ <style>${SHARED_CSS}</style>
416
+ </head>
417
+ <body class="viz-root">
418
+ <h1>${title}</h1>
419
+ <p class="sub" id="sub">Waiting for data\u2026</p>
420
+ <div class="rows" id="viz"><div class="empty">Loading\u2026</div></div>
421
+ <p class="foot">SentientUI \xB7 rendered by your agent host</p>
422
+ <script>
423
+ ${HELPERS_JS}
424
+ ${RENDER_JS[id]}
425
+ ${BOOTSTRAP_JS}
426
+ </script>
427
+ </body>
428
+ </html>`;
429
+ }
430
+
431
+ // src/ui/index.ts
432
+ var UI_TOOL_VIZ = {
433
+ get_persona_breakdown: "persona-breakdown",
434
+ get_variant_performance: "variant-performance",
435
+ get_goal_funnel: "goal-funnel",
436
+ get_layout_stats: "layout-stats"
437
+ };
438
+ function uiResourceUri(id) {
439
+ return `ui://sentientui/${id}`;
440
+ }
441
+ function uiMeta(id) {
442
+ const uri = uiResourceUri(id);
443
+ return {
444
+ ui: {
445
+ resourceUri: uri,
446
+ preferredFrameSize: ["720px", "480px"]
447
+ },
448
+ "openai/outputTemplate": uri
449
+ };
450
+ }
451
+ function registerUiResources(server) {
452
+ for (const id of Object.values(UI_TOOL_VIZ)) {
453
+ server.registerResource(
454
+ id,
455
+ uiResourceUri(id),
456
+ {
457
+ title: `${VIZ_TITLES[id]} (UI)`,
458
+ description: `Interactive ${VIZ_TITLES[id]} view for agent hosts that support MCP UI.`,
459
+ mimeType: "text/html"
460
+ },
461
+ async (uri) => ({
462
+ contents: [
463
+ {
464
+ uri: uri.href,
465
+ mimeType: "text/html",
466
+ text: buildTemplate(id)
467
+ }
468
+ ]
469
+ })
470
+ );
471
+ }
472
+ }
473
+
474
+ // src/tools/components.ts
218
475
  var projectIdSchema2 = z2.string().uuid().describe("The project UUID");
219
476
  function registerComponentTools(server, client) {
220
477
  server.registerTool(
@@ -268,6 +525,7 @@ function registerComponentTools(server, client) {
268
525
  title: "Variant performance",
269
526
  description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
270
527
  inputSchema: { projectId: projectIdSchema2 },
528
+ _meta: uiMeta("variant-performance"),
271
529
  outputSchema: {
272
530
  variants: z2.array(
273
531
  z2.object({
@@ -301,13 +559,14 @@ function registerComponentTools(server, client) {
301
559
  if (!data.cvr?.length) {
302
560
  return {
303
561
  content: [{ type: "text", text: "No variant data available yet." }],
304
- structuredContent
562
+ structuredContent,
563
+ _meta: uiMeta("variant-performance")
305
564
  };
306
565
  }
307
566
  const text = data.cvr.map(
308
567
  (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"})`
309
568
  ).join("\n");
310
- return { content: [{ type: "text", text }], structuredContent };
569
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
311
570
  }
312
571
  );
313
572
  }
@@ -387,6 +646,7 @@ function registerPersonaTools(server, client) {
387
646
  title: "Persona breakdown",
388
647
  description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
389
648
  inputSchema: { projectId: projectIdSchema4 },
649
+ _meta: uiMeta("persona-breakdown"),
390
650
  outputSchema: {
391
651
  totalSessions: z4.number().describe("Total sessions across all clusters"),
392
652
  clusters: z4.array(
@@ -419,7 +679,8 @@ function registerPersonaTools(server, client) {
419
679
  if (!data.clusters.length) {
420
680
  return {
421
681
  content: [{ type: "text", text: "No persona clusters yet. More visitor data is needed." }],
422
- structuredContent
682
+ structuredContent,
683
+ _meta: uiMeta("persona-breakdown")
423
684
  };
424
685
  }
425
686
  const lines = [
@@ -431,7 +692,7 @@ function registerPersonaTools(server, client) {
431
692
  return `- ${c.label}: ${c.sessionCount} sessions (${pct.toFixed(1)}% of traffic, reliability ${(c.avgReliability * 100).toFixed(0)}%)`;
432
693
  })
433
694
  ];
434
- return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
695
+ return { content: [{ type: "text", text: lines.join("\n") }], structuredContent, _meta: uiMeta("persona-breakdown") };
435
696
  }
436
697
  );
437
698
  }
@@ -446,6 +707,7 @@ function registerGoalTools(server, client) {
446
707
  title: "Goal funnel",
447
708
  description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
448
709
  inputSchema: { projectId: projectIdSchema5 },
710
+ _meta: uiMeta("goal-funnel"),
449
711
  outputSchema: {
450
712
  goals: z5.array(
451
713
  z5.object({
@@ -488,7 +750,8 @@ function registerGoalTools(server, client) {
488
750
  if (!data.goals.length) {
489
751
  return {
490
752
  content: [{ type: "text", text: "No goals configured for this project." }],
491
- structuredContent
753
+ structuredContent,
754
+ _meta: uiMeta("goal-funnel")
492
755
  };
493
756
  }
494
757
  const lines = data.goals.flatMap((g) => [
@@ -496,7 +759,7 @@ function registerGoalTools(server, client) {
496
759
  ...g.variants.map((v) => ` ${v.componentId}/${v.variantId}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
497
760
  ""
498
761
  ]);
499
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
762
+ return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
500
763
  }
501
764
  );
502
765
  }
@@ -560,6 +823,7 @@ function registerLayoutTools(server, client) {
560
823
  title: "Layout stats",
561
824
  description: "Get per-persona section layout rankings and bandit reward weights.",
562
825
  inputSchema: { projectId: projectIdSchema7 },
826
+ _meta: uiMeta("layout-stats"),
563
827
  outputSchema: {
564
828
  layouts: z7.array(
565
829
  z7.object({
@@ -590,13 +854,14 @@ function registerLayoutTools(server, client) {
590
854
  if (!stats.length) {
591
855
  return {
592
856
  content: [{ type: "text", text: "No layout data yet. More visitor sessions are needed." }],
593
- structuredContent
857
+ structuredContent,
858
+ _meta: uiMeta("layout-stats")
594
859
  };
595
860
  }
596
861
  const text = stats.map(
597
862
  (s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
598
863
  ).join("\n");
599
- return { content: [{ type: "text", text }], structuredContent };
864
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("layout-stats") };
600
865
  }
601
866
  );
602
867
  }
@@ -1117,6 +1382,7 @@ function createMcpServer(client) {
1117
1382
  name: "@sentientui/mcp",
1118
1383
  version: PKG_VERSION
1119
1384
  });
1385
+ registerUiResources(server);
1120
1386
  registerProjectTools(server, client);
1121
1387
  registerComponentTools(server, client);
1122
1388
  registerInsightTools(server, client);
package/dist/index.cjs CHANGED
@@ -223,6 +223,263 @@ function registerProjectTools(server, client) {
223
223
 
224
224
  // src/tools/components.ts
225
225
  var import_zod2 = require("zod");
226
+
227
+ // src/ui/templates.ts
228
+ var VIZ_TITLES = {
229
+ "persona-breakdown": "Persona breakdown",
230
+ "variant-performance": "Variant performance",
231
+ "goal-funnel": "Goal funnel",
232
+ "layout-stats": "Layout stats"
233
+ };
234
+ var SHARED_CSS = `
235
+ :root {
236
+ --surface: #fcfcfb;
237
+ --plane: #f9f9f7;
238
+ --ink: #0b0b0b;
239
+ --ink-2: #52514e;
240
+ --muted: #898781;
241
+ --track: #ecebe6;
242
+ --border: rgba(11,11,11,0.10);
243
+ --series-1: #2a78d6;
244
+ --good: #006300;
245
+ --critical: #c0392b;
246
+ }
247
+ @media (prefers-color-scheme: dark) {
248
+ :root {
249
+ --surface: #1a1a19;
250
+ --plane: #0d0d0d;
251
+ --ink: #ffffff;
252
+ --ink-2: #c3c2b7;
253
+ --muted: #898781;
254
+ --track: #2c2c2a;
255
+ --border: rgba(255,255,255,0.10);
256
+ --series-1: #3987e5;
257
+ --good: #0ca30c;
258
+ --critical: #e06a5c;
259
+ }
260
+ }
261
+ * { box-sizing: border-box; }
262
+ html, body { margin: 0; }
263
+ body {
264
+ background: var(--surface);
265
+ color: var(--ink);
266
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
267
+ font-size: 14px;
268
+ line-height: 1.45;
269
+ padding: 16px;
270
+ }
271
+ h1 { font-size: 15px; font-weight: 650; margin: 0 0 2px; }
272
+ .sub { color: var(--ink-2); font-size: 12px; margin: 0 0 14px; }
273
+ .rows { display: flex; flex-direction: column; gap: 10px; }
274
+ .row { display: grid; grid-template-columns: 1fr; gap: 4px; }
275
+ .row-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
276
+ .label { font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
277
+ .val { color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
278
+ .track {
279
+ position: relative; height: 14px; border-radius: 4px;
280
+ background: var(--track); overflow: hidden;
281
+ }
282
+ .bar {
283
+ position: absolute; left: 0; top: 0; bottom: 0;
284
+ background: var(--series-1); border-radius: 4px; min-width: 3px;
285
+ }
286
+ .meta { color: var(--ink-2); font-size: 12px; }
287
+ .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
288
+ .chip {
289
+ border: 1px solid var(--border); border-radius: 999px;
290
+ padding: 2px 9px; font-size: 12px; color: var(--ink-2);
291
+ display: inline-flex; align-items: center; gap: 6px;
292
+ }
293
+ .chip .rank { color: var(--muted); font-variant-numeric: tabular-nums; }
294
+ .delta { font-variant-numeric: tabular-nums; font-weight: 550; white-space: nowrap; }
295
+ .delta.up { color: var(--good); }
296
+ .delta.down { color: var(--critical); }
297
+ .delta.flat { color: var(--muted); }
298
+ .empty { color: var(--ink-2); padding: 8px 0; }
299
+ .foot { color: var(--muted); font-size: 11px; margin-top: 14px; }
300
+ `;
301
+ var HELPERS_JS = `
302
+ function esc(s){ return String(s == null ? '' : s).replace(/[&<>"']/g, function(c){
303
+ return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]; }); }
304
+ function pct1(n){ return (Number(n) || 0).toFixed(1) + '%'; }
305
+ function clampPct(n){ n = Number(n) || 0; return n < 0 ? 0 : n > 100 ? 100 : n; }
306
+ function el(id){ return document.getElementById(id); }
307
+ function barRow(label, valText, widthPct, metaText){
308
+ return '<div class="row"><div class="row-head"><span class="label">' + esc(label) +
309
+ '</span><span class="val">' + esc(valText) + '</span></div>' +
310
+ '<div class="track"><span class="bar" style="width:' + clampPct(widthPct) + '%"></span></div>' +
311
+ (metaText ? '<div class="meta">' + esc(metaText) + '</div>' : '') + '</div>';
312
+ }
313
+ function showEmpty(msg){ el('viz').innerHTML = '<div class="empty">' + esc(msg) + '</div>'; }
314
+ `;
315
+ var BOOTSTRAP_JS = `
316
+ function __extract(m){
317
+ if (!m) return null;
318
+ if (m.structuredContent) return m.structuredContent;
319
+ if (m.toolOutput) return m.toolOutput;
320
+ if (m.globals && m.globals.toolOutput) return m.globals.toolOutput;
321
+ if (m.payload && m.payload.renderData) return __extract(m.payload.renderData);
322
+ if (m.renderData) return __extract(m.renderData);
323
+ return m;
324
+ }
325
+ function __boot(){
326
+ try {
327
+ var g = (window.openai && window.openai.toolOutput) || window.mcpUiRenderData ||
328
+ (window.__MCP_UI__ && window.__MCP_UI__.toolOutput);
329
+ if (g) { window.__render(g); return; }
330
+ } catch (e) {}
331
+ window.addEventListener('message', function(ev){
332
+ var data = __extract(ev && ev.data);
333
+ if (data && typeof data === 'object') { try { window.__render(data); } catch (e) {} }
334
+ });
335
+ try {
336
+ window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');
337
+ window.parent.postMessage({ type: 'openai:request_globals' }, '*');
338
+ } catch (e) {}
339
+ }
340
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', __boot);
341
+ else __boot();
342
+ `;
343
+ var RENDER_JS = {
344
+ "persona-breakdown": `
345
+ window.__render = function(data){
346
+ var clusters = (data && data.clusters) || [];
347
+ var total = (data && data.totalSessions) || 0;
348
+ el('sub').textContent = total + ' total sessions across ' + clusters.length + ' persona' + (clusters.length === 1 ? '' : 's');
349
+ if (!clusters.length) { showEmpty('No persona clusters yet \u2014 more visitor data is needed.'); return; }
350
+ var sorted = clusters.slice().sort(function(a,b){ return (b.sharePct||0) - (a.sharePct||0); });
351
+ el('viz').innerHTML = sorted.map(function(c){
352
+ var rel = Math.round((c.reliability || 0) * 100);
353
+ return barRow(c.label, pct1(c.sharePct), c.sharePct,
354
+ c.sessionCount + ' sessions \xB7 reliability ' + rel + '%');
355
+ }).join('');
356
+ };
357
+ `,
358
+ "variant-performance": `
359
+ window.__render = function(data){
360
+ var variants = (data && data.variants) || [];
361
+ el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, last 7 days vs prior 7';
362
+ if (!variants.length) { showEmpty('No variant data available yet.'); return; }
363
+ var max = variants.reduce(function(m,v){ return Math.max(m, v.currentCvr || 0); }, 0) || 1;
364
+ var sorted = variants.slice().sort(function(a,b){ return (b.currentCvr||0) - (a.currentCvr||0); });
365
+ el('viz').innerHTML = sorted.map(function(v){
366
+ var cur = (v.currentCvr || 0) * 100;
367
+ var dp = Number(v.deltaPp) || 0;
368
+ var dir = dp > 0.05 ? 'up' : dp < -0.05 ? 'down' : 'flat';
369
+ var arrow = dir === 'up' ? '\u25B2' : dir === 'down' ? '\u25BC' : '\u2014';
370
+ var sign = dp > 0 ? '+' : '';
371
+ var deltaHtml = '<span class="delta ' + dir + '">' + arrow + ' ' + sign + dp.toFixed(1) + ' pp</span>';
372
+ var head = '<div class="row-head"><span class="label">' + esc(v.variantId) +
373
+ '</span><span class="val">' + cur.toFixed(2) + '% ' + deltaHtml + '</span></div>';
374
+ return '<div class="row">' + head +
375
+ '<div class="track"><span class="bar" style="width:' + clampPct((v.currentCvr || 0) / max * 100) + '%"></span></div>' +
376
+ '<div class="meta">prior ' + ((v.priorCvr||0)*100).toFixed(2) + '% \xB7 momentum ' + esc(v.momentum || 'stable') + '</div></div>';
377
+ }).join('');
378
+ };
379
+ `,
380
+ "goal-funnel": `
381
+ window.__render = function(data){
382
+ var goals = (data && data.goals) || [];
383
+ el('sub').textContent = goals.length + ' goal' + (goals.length === 1 ? '' : 's') + ' \xB7 unique-session conversion';
384
+ if (!goals.length) { showEmpty('No goals configured for this project.'); return; }
385
+ el('viz').innerHTML = goals.map(function(g){
386
+ var cr = (g.conversionRate || 0) * 100;
387
+ var best = (g.variants || []).slice().sort(function(a,b){ return (b.completionRate||0)-(a.completionRate||0); })[0];
388
+ var meta = g.hits + ' hits \xB7 ' + g.uniqueSessions + ' unique sessions' +
389
+ (best ? ' \xB7 best: ' + esc(best.componentId + '/' + best.variantId) + ' ' + ((best.completionRate||0)*100).toFixed(1) + '%' : '');
390
+ return barRow(g.goalName, pct1(cr), cr, meta);
391
+ }).join('');
392
+ };
393
+ `,
394
+ "layout-stats": `
395
+ window.__render = function(data){
396
+ var layouts = (data && data.layouts) || [];
397
+ el('sub').textContent = layouts.length + ' persona' + (layouts.length === 1 ? '' : 's') + ' \xB7 section order by reward';
398
+ if (!layouts.length) { showEmpty('No layout data yet \u2014 more visitor sessions are needed.'); return; }
399
+ var max = layouts.reduce(function(m,l){ return Math.max(m, l.avgReward || 0); }, 0) || 1;
400
+ var sorted = layouts.slice().sort(function(a,b){ return (b.avgReward||0)-(a.avgReward||0); });
401
+ el('viz').innerHTML = sorted.map(function(l){
402
+ var order = (l.layoutOrder || []).map(function(s, i){
403
+ return '<span class="chip"><span class="rank">' + (i+1) + '</span>' + esc(s) + '</span>';
404
+ }).join('');
405
+ var head = '<div class="row-head"><span class="label">' + esc(l.persona) +
406
+ '</span><span class="val">reward ' + (Number(l.avgReward)||0).toFixed(2) + '</span></div>';
407
+ return '<div class="row">' + head +
408
+ '<div class="track"><span class="bar" style="width:' + clampPct((l.avgReward||0)/max*100) + '%"></span></div>' +
409
+ '<div class="chips">' + order + '</div>' +
410
+ '<div class="meta">' + (l.pulls || 0) + ' pulls</div></div>';
411
+ }).join('');
412
+ };
413
+ `
414
+ };
415
+ function buildTemplate(id) {
416
+ const title = VIZ_TITLES[id];
417
+ return `<!doctype html>
418
+ <html lang="en">
419
+ <head>
420
+ <meta charset="utf-8">
421
+ <meta name="viewport" content="width=device-width, initial-scale=1">
422
+ <title>${title} \u2014 SentientUI</title>
423
+ <style>${SHARED_CSS}</style>
424
+ </head>
425
+ <body class="viz-root">
426
+ <h1>${title}</h1>
427
+ <p class="sub" id="sub">Waiting for data\u2026</p>
428
+ <div class="rows" id="viz"><div class="empty">Loading\u2026</div></div>
429
+ <p class="foot">SentientUI \xB7 rendered by your agent host</p>
430
+ <script>
431
+ ${HELPERS_JS}
432
+ ${RENDER_JS[id]}
433
+ ${BOOTSTRAP_JS}
434
+ </script>
435
+ </body>
436
+ </html>`;
437
+ }
438
+
439
+ // src/ui/index.ts
440
+ var UI_TOOL_VIZ = {
441
+ get_persona_breakdown: "persona-breakdown",
442
+ get_variant_performance: "variant-performance",
443
+ get_goal_funnel: "goal-funnel",
444
+ get_layout_stats: "layout-stats"
445
+ };
446
+ function uiResourceUri(id) {
447
+ return `ui://sentientui/${id}`;
448
+ }
449
+ function uiMeta(id) {
450
+ const uri = uiResourceUri(id);
451
+ return {
452
+ ui: {
453
+ resourceUri: uri,
454
+ preferredFrameSize: ["720px", "480px"]
455
+ },
456
+ "openai/outputTemplate": uri
457
+ };
458
+ }
459
+ function registerUiResources(server) {
460
+ for (const id of Object.values(UI_TOOL_VIZ)) {
461
+ server.registerResource(
462
+ id,
463
+ uiResourceUri(id),
464
+ {
465
+ title: `${VIZ_TITLES[id]} (UI)`,
466
+ description: `Interactive ${VIZ_TITLES[id]} view for agent hosts that support MCP UI.`,
467
+ mimeType: "text/html"
468
+ },
469
+ async (uri) => ({
470
+ contents: [
471
+ {
472
+ uri: uri.href,
473
+ mimeType: "text/html",
474
+ text: buildTemplate(id)
475
+ }
476
+ ]
477
+ })
478
+ );
479
+ }
480
+ }
481
+
482
+ // src/tools/components.ts
226
483
  var projectIdSchema2 = import_zod2.z.string().uuid().describe("The project UUID");
227
484
  function registerComponentTools(server, client) {
228
485
  server.registerTool(
@@ -276,6 +533,7 @@ function registerComponentTools(server, client) {
276
533
  title: "Variant performance",
277
534
  description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
278
535
  inputSchema: { projectId: projectIdSchema2 },
536
+ _meta: uiMeta("variant-performance"),
279
537
  outputSchema: {
280
538
  variants: import_zod2.z.array(
281
539
  import_zod2.z.object({
@@ -309,13 +567,14 @@ function registerComponentTools(server, client) {
309
567
  if (!data.cvr?.length) {
310
568
  return {
311
569
  content: [{ type: "text", text: "No variant data available yet." }],
312
- structuredContent
570
+ structuredContent,
571
+ _meta: uiMeta("variant-performance")
313
572
  };
314
573
  }
315
574
  const text = data.cvr.map(
316
575
  (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"})`
317
576
  ).join("\n");
318
- return { content: [{ type: "text", text }], structuredContent };
577
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
319
578
  }
320
579
  );
321
580
  }
@@ -395,6 +654,7 @@ function registerPersonaTools(server, client) {
395
654
  title: "Persona breakdown",
396
655
  description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
397
656
  inputSchema: { projectId: projectIdSchema4 },
657
+ _meta: uiMeta("persona-breakdown"),
398
658
  outputSchema: {
399
659
  totalSessions: import_zod4.z.number().describe("Total sessions across all clusters"),
400
660
  clusters: import_zod4.z.array(
@@ -427,7 +687,8 @@ function registerPersonaTools(server, client) {
427
687
  if (!data.clusters.length) {
428
688
  return {
429
689
  content: [{ type: "text", text: "No persona clusters yet. More visitor data is needed." }],
430
- structuredContent
690
+ structuredContent,
691
+ _meta: uiMeta("persona-breakdown")
431
692
  };
432
693
  }
433
694
  const lines = [
@@ -439,7 +700,7 @@ function registerPersonaTools(server, client) {
439
700
  return `- ${c.label}: ${c.sessionCount} sessions (${pct.toFixed(1)}% of traffic, reliability ${(c.avgReliability * 100).toFixed(0)}%)`;
440
701
  })
441
702
  ];
442
- return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
703
+ return { content: [{ type: "text", text: lines.join("\n") }], structuredContent, _meta: uiMeta("persona-breakdown") };
443
704
  }
444
705
  );
445
706
  }
@@ -454,6 +715,7 @@ function registerGoalTools(server, client) {
454
715
  title: "Goal funnel",
455
716
  description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
456
717
  inputSchema: { projectId: projectIdSchema5 },
718
+ _meta: uiMeta("goal-funnel"),
457
719
  outputSchema: {
458
720
  goals: import_zod5.z.array(
459
721
  import_zod5.z.object({
@@ -496,7 +758,8 @@ function registerGoalTools(server, client) {
496
758
  if (!data.goals.length) {
497
759
  return {
498
760
  content: [{ type: "text", text: "No goals configured for this project." }],
499
- structuredContent
761
+ structuredContent,
762
+ _meta: uiMeta("goal-funnel")
500
763
  };
501
764
  }
502
765
  const lines = data.goals.flatMap((g) => [
@@ -504,7 +767,7 @@ function registerGoalTools(server, client) {
504
767
  ...g.variants.map((v) => ` ${v.componentId}/${v.variantId}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
505
768
  ""
506
769
  ]);
507
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
770
+ return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
508
771
  }
509
772
  );
510
773
  }
@@ -568,6 +831,7 @@ function registerLayoutTools(server, client) {
568
831
  title: "Layout stats",
569
832
  description: "Get per-persona section layout rankings and bandit reward weights.",
570
833
  inputSchema: { projectId: projectIdSchema7 },
834
+ _meta: uiMeta("layout-stats"),
571
835
  outputSchema: {
572
836
  layouts: import_zod7.z.array(
573
837
  import_zod7.z.object({
@@ -598,13 +862,14 @@ function registerLayoutTools(server, client) {
598
862
  if (!stats.length) {
599
863
  return {
600
864
  content: [{ type: "text", text: "No layout data yet. More visitor sessions are needed." }],
601
- structuredContent
865
+ structuredContent,
866
+ _meta: uiMeta("layout-stats")
602
867
  };
603
868
  }
604
869
  const text = stats.map(
605
870
  (s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
606
871
  ).join("\n");
607
- return { content: [{ type: "text", text }], structuredContent };
872
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("layout-stats") };
608
873
  }
609
874
  );
610
875
  }
@@ -1125,6 +1390,7 @@ function createMcpServer(client) {
1125
1390
  name: "@sentientui/mcp",
1126
1391
  version: PKG_VERSION
1127
1392
  });
1393
+ registerUiResources(server);
1128
1394
  registerProjectTools(server, client);
1129
1395
  registerComponentTools(server, client);
1130
1396
  registerInsightTools(server, client);
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ApiClient,
4
4
  createMcpServer
5
- } from "./chunk-OPMHTLTY.js";
5
+ } from "./chunk-LZDDLCPW.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/dist/lib.cjs CHANGED
@@ -248,6 +248,263 @@ function registerProjectTools(server, client) {
248
248
 
249
249
  // src/tools/components.ts
250
250
  var import_zod2 = require("zod");
251
+
252
+ // src/ui/templates.ts
253
+ var VIZ_TITLES = {
254
+ "persona-breakdown": "Persona breakdown",
255
+ "variant-performance": "Variant performance",
256
+ "goal-funnel": "Goal funnel",
257
+ "layout-stats": "Layout stats"
258
+ };
259
+ var SHARED_CSS = `
260
+ :root {
261
+ --surface: #fcfcfb;
262
+ --plane: #f9f9f7;
263
+ --ink: #0b0b0b;
264
+ --ink-2: #52514e;
265
+ --muted: #898781;
266
+ --track: #ecebe6;
267
+ --border: rgba(11,11,11,0.10);
268
+ --series-1: #2a78d6;
269
+ --good: #006300;
270
+ --critical: #c0392b;
271
+ }
272
+ @media (prefers-color-scheme: dark) {
273
+ :root {
274
+ --surface: #1a1a19;
275
+ --plane: #0d0d0d;
276
+ --ink: #ffffff;
277
+ --ink-2: #c3c2b7;
278
+ --muted: #898781;
279
+ --track: #2c2c2a;
280
+ --border: rgba(255,255,255,0.10);
281
+ --series-1: #3987e5;
282
+ --good: #0ca30c;
283
+ --critical: #e06a5c;
284
+ }
285
+ }
286
+ * { box-sizing: border-box; }
287
+ html, body { margin: 0; }
288
+ body {
289
+ background: var(--surface);
290
+ color: var(--ink);
291
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
292
+ font-size: 14px;
293
+ line-height: 1.45;
294
+ padding: 16px;
295
+ }
296
+ h1 { font-size: 15px; font-weight: 650; margin: 0 0 2px; }
297
+ .sub { color: var(--ink-2); font-size: 12px; margin: 0 0 14px; }
298
+ .rows { display: flex; flex-direction: column; gap: 10px; }
299
+ .row { display: grid; grid-template-columns: 1fr; gap: 4px; }
300
+ .row-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
301
+ .label { font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
302
+ .val { color: var(--ink); font-variant-numeric: tabular-nums; white-space: nowrap; }
303
+ .track {
304
+ position: relative; height: 14px; border-radius: 4px;
305
+ background: var(--track); overflow: hidden;
306
+ }
307
+ .bar {
308
+ position: absolute; left: 0; top: 0; bottom: 0;
309
+ background: var(--series-1); border-radius: 4px; min-width: 3px;
310
+ }
311
+ .meta { color: var(--ink-2); font-size: 12px; }
312
+ .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
313
+ .chip {
314
+ border: 1px solid var(--border); border-radius: 999px;
315
+ padding: 2px 9px; font-size: 12px; color: var(--ink-2);
316
+ display: inline-flex; align-items: center; gap: 6px;
317
+ }
318
+ .chip .rank { color: var(--muted); font-variant-numeric: tabular-nums; }
319
+ .delta { font-variant-numeric: tabular-nums; font-weight: 550; white-space: nowrap; }
320
+ .delta.up { color: var(--good); }
321
+ .delta.down { color: var(--critical); }
322
+ .delta.flat { color: var(--muted); }
323
+ .empty { color: var(--ink-2); padding: 8px 0; }
324
+ .foot { color: var(--muted); font-size: 11px; margin-top: 14px; }
325
+ `;
326
+ var HELPERS_JS = `
327
+ function esc(s){ return String(s == null ? '' : s).replace(/[&<>"']/g, function(c){
328
+ return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]; }); }
329
+ function pct1(n){ return (Number(n) || 0).toFixed(1) + '%'; }
330
+ function clampPct(n){ n = Number(n) || 0; return n < 0 ? 0 : n > 100 ? 100 : n; }
331
+ function el(id){ return document.getElementById(id); }
332
+ function barRow(label, valText, widthPct, metaText){
333
+ return '<div class="row"><div class="row-head"><span class="label">' + esc(label) +
334
+ '</span><span class="val">' + esc(valText) + '</span></div>' +
335
+ '<div class="track"><span class="bar" style="width:' + clampPct(widthPct) + '%"></span></div>' +
336
+ (metaText ? '<div class="meta">' + esc(metaText) + '</div>' : '') + '</div>';
337
+ }
338
+ function showEmpty(msg){ el('viz').innerHTML = '<div class="empty">' + esc(msg) + '</div>'; }
339
+ `;
340
+ var BOOTSTRAP_JS = `
341
+ function __extract(m){
342
+ if (!m) return null;
343
+ if (m.structuredContent) return m.structuredContent;
344
+ if (m.toolOutput) return m.toolOutput;
345
+ if (m.globals && m.globals.toolOutput) return m.globals.toolOutput;
346
+ if (m.payload && m.payload.renderData) return __extract(m.payload.renderData);
347
+ if (m.renderData) return __extract(m.renderData);
348
+ return m;
349
+ }
350
+ function __boot(){
351
+ try {
352
+ var g = (window.openai && window.openai.toolOutput) || window.mcpUiRenderData ||
353
+ (window.__MCP_UI__ && window.__MCP_UI__.toolOutput);
354
+ if (g) { window.__render(g); return; }
355
+ } catch (e) {}
356
+ window.addEventListener('message', function(ev){
357
+ var data = __extract(ev && ev.data);
358
+ if (data && typeof data === 'object') { try { window.__render(data); } catch (e) {} }
359
+ });
360
+ try {
361
+ window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');
362
+ window.parent.postMessage({ type: 'openai:request_globals' }, '*');
363
+ } catch (e) {}
364
+ }
365
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', __boot);
366
+ else __boot();
367
+ `;
368
+ var RENDER_JS = {
369
+ "persona-breakdown": `
370
+ window.__render = function(data){
371
+ var clusters = (data && data.clusters) || [];
372
+ var total = (data && data.totalSessions) || 0;
373
+ el('sub').textContent = total + ' total sessions across ' + clusters.length + ' persona' + (clusters.length === 1 ? '' : 's');
374
+ if (!clusters.length) { showEmpty('No persona clusters yet \u2014 more visitor data is needed.'); return; }
375
+ var sorted = clusters.slice().sort(function(a,b){ return (b.sharePct||0) - (a.sharePct||0); });
376
+ el('viz').innerHTML = sorted.map(function(c){
377
+ var rel = Math.round((c.reliability || 0) * 100);
378
+ return barRow(c.label, pct1(c.sharePct), c.sharePct,
379
+ c.sessionCount + ' sessions \xB7 reliability ' + rel + '%');
380
+ }).join('');
381
+ };
382
+ `,
383
+ "variant-performance": `
384
+ window.__render = function(data){
385
+ var variants = (data && data.variants) || [];
386
+ el('sub').textContent = variants.length + ' variant' + (variants.length === 1 ? '' : 's') + ' \xB7 conversion, last 7 days vs prior 7';
387
+ if (!variants.length) { showEmpty('No variant data available yet.'); return; }
388
+ var max = variants.reduce(function(m,v){ return Math.max(m, v.currentCvr || 0); }, 0) || 1;
389
+ var sorted = variants.slice().sort(function(a,b){ return (b.currentCvr||0) - (a.currentCvr||0); });
390
+ el('viz').innerHTML = sorted.map(function(v){
391
+ var cur = (v.currentCvr || 0) * 100;
392
+ var dp = Number(v.deltaPp) || 0;
393
+ var dir = dp > 0.05 ? 'up' : dp < -0.05 ? 'down' : 'flat';
394
+ var arrow = dir === 'up' ? '\u25B2' : dir === 'down' ? '\u25BC' : '\u2014';
395
+ var sign = dp > 0 ? '+' : '';
396
+ var deltaHtml = '<span class="delta ' + dir + '">' + arrow + ' ' + sign + dp.toFixed(1) + ' pp</span>';
397
+ var head = '<div class="row-head"><span class="label">' + esc(v.variantId) +
398
+ '</span><span class="val">' + cur.toFixed(2) + '% ' + deltaHtml + '</span></div>';
399
+ return '<div class="row">' + head +
400
+ '<div class="track"><span class="bar" style="width:' + clampPct((v.currentCvr || 0) / max * 100) + '%"></span></div>' +
401
+ '<div class="meta">prior ' + ((v.priorCvr||0)*100).toFixed(2) + '% \xB7 momentum ' + esc(v.momentum || 'stable') + '</div></div>';
402
+ }).join('');
403
+ };
404
+ `,
405
+ "goal-funnel": `
406
+ window.__render = function(data){
407
+ var goals = (data && data.goals) || [];
408
+ el('sub').textContent = goals.length + ' goal' + (goals.length === 1 ? '' : 's') + ' \xB7 unique-session conversion';
409
+ if (!goals.length) { showEmpty('No goals configured for this project.'); return; }
410
+ el('viz').innerHTML = goals.map(function(g){
411
+ var cr = (g.conversionRate || 0) * 100;
412
+ var best = (g.variants || []).slice().sort(function(a,b){ return (b.completionRate||0)-(a.completionRate||0); })[0];
413
+ var meta = g.hits + ' hits \xB7 ' + g.uniqueSessions + ' unique sessions' +
414
+ (best ? ' \xB7 best: ' + esc(best.componentId + '/' + best.variantId) + ' ' + ((best.completionRate||0)*100).toFixed(1) + '%' : '');
415
+ return barRow(g.goalName, pct1(cr), cr, meta);
416
+ }).join('');
417
+ };
418
+ `,
419
+ "layout-stats": `
420
+ window.__render = function(data){
421
+ var layouts = (data && data.layouts) || [];
422
+ el('sub').textContent = layouts.length + ' persona' + (layouts.length === 1 ? '' : 's') + ' \xB7 section order by reward';
423
+ if (!layouts.length) { showEmpty('No layout data yet \u2014 more visitor sessions are needed.'); return; }
424
+ var max = layouts.reduce(function(m,l){ return Math.max(m, l.avgReward || 0); }, 0) || 1;
425
+ var sorted = layouts.slice().sort(function(a,b){ return (b.avgReward||0)-(a.avgReward||0); });
426
+ el('viz').innerHTML = sorted.map(function(l){
427
+ var order = (l.layoutOrder || []).map(function(s, i){
428
+ return '<span class="chip"><span class="rank">' + (i+1) + '</span>' + esc(s) + '</span>';
429
+ }).join('');
430
+ var head = '<div class="row-head"><span class="label">' + esc(l.persona) +
431
+ '</span><span class="val">reward ' + (Number(l.avgReward)||0).toFixed(2) + '</span></div>';
432
+ return '<div class="row">' + head +
433
+ '<div class="track"><span class="bar" style="width:' + clampPct((l.avgReward||0)/max*100) + '%"></span></div>' +
434
+ '<div class="chips">' + order + '</div>' +
435
+ '<div class="meta">' + (l.pulls || 0) + ' pulls</div></div>';
436
+ }).join('');
437
+ };
438
+ `
439
+ };
440
+ function buildTemplate(id) {
441
+ const title = VIZ_TITLES[id];
442
+ return `<!doctype html>
443
+ <html lang="en">
444
+ <head>
445
+ <meta charset="utf-8">
446
+ <meta name="viewport" content="width=device-width, initial-scale=1">
447
+ <title>${title} \u2014 SentientUI</title>
448
+ <style>${SHARED_CSS}</style>
449
+ </head>
450
+ <body class="viz-root">
451
+ <h1>${title}</h1>
452
+ <p class="sub" id="sub">Waiting for data\u2026</p>
453
+ <div class="rows" id="viz"><div class="empty">Loading\u2026</div></div>
454
+ <p class="foot">SentientUI \xB7 rendered by your agent host</p>
455
+ <script>
456
+ ${HELPERS_JS}
457
+ ${RENDER_JS[id]}
458
+ ${BOOTSTRAP_JS}
459
+ </script>
460
+ </body>
461
+ </html>`;
462
+ }
463
+
464
+ // src/ui/index.ts
465
+ var UI_TOOL_VIZ = {
466
+ get_persona_breakdown: "persona-breakdown",
467
+ get_variant_performance: "variant-performance",
468
+ get_goal_funnel: "goal-funnel",
469
+ get_layout_stats: "layout-stats"
470
+ };
471
+ function uiResourceUri(id) {
472
+ return `ui://sentientui/${id}`;
473
+ }
474
+ function uiMeta(id) {
475
+ const uri = uiResourceUri(id);
476
+ return {
477
+ ui: {
478
+ resourceUri: uri,
479
+ preferredFrameSize: ["720px", "480px"]
480
+ },
481
+ "openai/outputTemplate": uri
482
+ };
483
+ }
484
+ function registerUiResources(server) {
485
+ for (const id of Object.values(UI_TOOL_VIZ)) {
486
+ server.registerResource(
487
+ id,
488
+ uiResourceUri(id),
489
+ {
490
+ title: `${VIZ_TITLES[id]} (UI)`,
491
+ description: `Interactive ${VIZ_TITLES[id]} view for agent hosts that support MCP UI.`,
492
+ mimeType: "text/html"
493
+ },
494
+ async (uri) => ({
495
+ contents: [
496
+ {
497
+ uri: uri.href,
498
+ mimeType: "text/html",
499
+ text: buildTemplate(id)
500
+ }
501
+ ]
502
+ })
503
+ );
504
+ }
505
+ }
506
+
507
+ // src/tools/components.ts
251
508
  var projectIdSchema2 = import_zod2.z.string().uuid().describe("The project UUID");
252
509
  function registerComponentTools(server, client) {
253
510
  server.registerTool(
@@ -301,6 +558,7 @@ function registerComponentTools(server, client) {
301
558
  title: "Variant performance",
302
559
  description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
303
560
  inputSchema: { projectId: projectIdSchema2 },
561
+ _meta: uiMeta("variant-performance"),
304
562
  outputSchema: {
305
563
  variants: import_zod2.z.array(
306
564
  import_zod2.z.object({
@@ -334,13 +592,14 @@ function registerComponentTools(server, client) {
334
592
  if (!data.cvr?.length) {
335
593
  return {
336
594
  content: [{ type: "text", text: "No variant data available yet." }],
337
- structuredContent
595
+ structuredContent,
596
+ _meta: uiMeta("variant-performance")
338
597
  };
339
598
  }
340
599
  const text = data.cvr.map(
341
600
  (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"})`
342
601
  ).join("\n");
343
- return { content: [{ type: "text", text }], structuredContent };
602
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
344
603
  }
345
604
  );
346
605
  }
@@ -420,6 +679,7 @@ function registerPersonaTools(server, client) {
420
679
  title: "Persona breakdown",
421
680
  description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
422
681
  inputSchema: { projectId: projectIdSchema4 },
682
+ _meta: uiMeta("persona-breakdown"),
423
683
  outputSchema: {
424
684
  totalSessions: import_zod4.z.number().describe("Total sessions across all clusters"),
425
685
  clusters: import_zod4.z.array(
@@ -452,7 +712,8 @@ function registerPersonaTools(server, client) {
452
712
  if (!data.clusters.length) {
453
713
  return {
454
714
  content: [{ type: "text", text: "No persona clusters yet. More visitor data is needed." }],
455
- structuredContent
715
+ structuredContent,
716
+ _meta: uiMeta("persona-breakdown")
456
717
  };
457
718
  }
458
719
  const lines = [
@@ -464,7 +725,7 @@ function registerPersonaTools(server, client) {
464
725
  return `- ${c.label}: ${c.sessionCount} sessions (${pct.toFixed(1)}% of traffic, reliability ${(c.avgReliability * 100).toFixed(0)}%)`;
465
726
  })
466
727
  ];
467
- return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
728
+ return { content: [{ type: "text", text: lines.join("\n") }], structuredContent, _meta: uiMeta("persona-breakdown") };
468
729
  }
469
730
  );
470
731
  }
@@ -479,6 +740,7 @@ function registerGoalTools(server, client) {
479
740
  title: "Goal funnel",
480
741
  description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
481
742
  inputSchema: { projectId: projectIdSchema5 },
743
+ _meta: uiMeta("goal-funnel"),
482
744
  outputSchema: {
483
745
  goals: import_zod5.z.array(
484
746
  import_zod5.z.object({
@@ -521,7 +783,8 @@ function registerGoalTools(server, client) {
521
783
  if (!data.goals.length) {
522
784
  return {
523
785
  content: [{ type: "text", text: "No goals configured for this project." }],
524
- structuredContent
786
+ structuredContent,
787
+ _meta: uiMeta("goal-funnel")
525
788
  };
526
789
  }
527
790
  const lines = data.goals.flatMap((g) => [
@@ -529,7 +792,7 @@ function registerGoalTools(server, client) {
529
792
  ...g.variants.map((v) => ` ${v.componentId}/${v.variantId}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
530
793
  ""
531
794
  ]);
532
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
795
+ return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
533
796
  }
534
797
  );
535
798
  }
@@ -593,6 +856,7 @@ function registerLayoutTools(server, client) {
593
856
  title: "Layout stats",
594
857
  description: "Get per-persona section layout rankings and bandit reward weights.",
595
858
  inputSchema: { projectId: projectIdSchema7 },
859
+ _meta: uiMeta("layout-stats"),
596
860
  outputSchema: {
597
861
  layouts: import_zod7.z.array(
598
862
  import_zod7.z.object({
@@ -623,13 +887,14 @@ function registerLayoutTools(server, client) {
623
887
  if (!stats.length) {
624
888
  return {
625
889
  content: [{ type: "text", text: "No layout data yet. More visitor sessions are needed." }],
626
- structuredContent
890
+ structuredContent,
891
+ _meta: uiMeta("layout-stats")
627
892
  };
628
893
  }
629
894
  const text = stats.map(
630
895
  (s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
631
896
  ).join("\n");
632
- return { content: [{ type: "text", text }], structuredContent };
897
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("layout-stats") };
633
898
  }
634
899
  );
635
900
  }
@@ -1150,6 +1415,7 @@ function createMcpServer(client) {
1150
1415
  name: "@sentientui/mcp",
1151
1416
  version: PKG_VERSION
1152
1417
  });
1418
+ registerUiResources(server);
1153
1419
  registerProjectTools(server, client);
1154
1420
  registerComponentTools(server, client);
1155
1421
  registerInsightTools(server, client);
package/dist/lib.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  ApiClient,
4
4
  ApiError,
5
5
  createMcpServer
6
- } from "./chunk-OPMHTLTY.js";
6
+ } from "./chunk-LZDDLCPW.js";
7
7
  export {
8
8
  ApiClient,
9
9
  ApiError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/mcp",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server for SentientUI — exposes project data and actions to AI agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentient-ui.com",