@sentientui/mcp 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -215,6 +215,264 @@ 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 RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
433
+ var UI_TOOL_VIZ = {
434
+ get_persona_breakdown: "persona-breakdown",
435
+ get_variant_performance: "variant-performance",
436
+ get_goal_funnel: "goal-funnel",
437
+ get_layout_stats: "layout-stats"
438
+ };
439
+ function uiResourceUri(id) {
440
+ return `ui://sentientui/${id}`;
441
+ }
442
+ function uiMeta(id) {
443
+ const uri = uiResourceUri(id);
444
+ return {
445
+ ui: {
446
+ resourceUri: uri,
447
+ preferredFrameSize: ["720px", "480px"]
448
+ },
449
+ "openai/outputTemplate": uri
450
+ };
451
+ }
452
+ function registerUiResources(server) {
453
+ for (const id of Object.values(UI_TOOL_VIZ)) {
454
+ server.registerResource(
455
+ id,
456
+ uiResourceUri(id),
457
+ {
458
+ title: `${VIZ_TITLES[id]} (UI)`,
459
+ description: `Interactive ${VIZ_TITLES[id]} view for agent hosts that support MCP UI.`,
460
+ mimeType: RESOURCE_MIME_TYPE
461
+ },
462
+ async (uri) => ({
463
+ contents: [
464
+ {
465
+ uri: uri.href,
466
+ mimeType: RESOURCE_MIME_TYPE,
467
+ text: buildTemplate(id)
468
+ }
469
+ ]
470
+ })
471
+ );
472
+ }
473
+ }
474
+
475
+ // src/tools/components.ts
218
476
  var projectIdSchema2 = z2.string().uuid().describe("The project UUID");
219
477
  function registerComponentTools(server, client) {
220
478
  server.registerTool(
@@ -268,6 +526,7 @@ function registerComponentTools(server, client) {
268
526
  title: "Variant performance",
269
527
  description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
270
528
  inputSchema: { projectId: projectIdSchema2 },
529
+ _meta: uiMeta("variant-performance"),
271
530
  outputSchema: {
272
531
  variants: z2.array(
273
532
  z2.object({
@@ -301,13 +560,14 @@ function registerComponentTools(server, client) {
301
560
  if (!data.cvr?.length) {
302
561
  return {
303
562
  content: [{ type: "text", text: "No variant data available yet." }],
304
- structuredContent
563
+ structuredContent,
564
+ _meta: uiMeta("variant-performance")
305
565
  };
306
566
  }
307
567
  const text = data.cvr.map(
308
568
  (v) => `- ${v.variantId}: CVR ${(v.currentCvr * 100).toFixed(2)}% (prior ${(v.priorCvr * 100).toFixed(2)}%, ${v.deltaPp > 0 ? "+" : ""}${v.deltaPp.toFixed(1)} pp, ${momentumMap.get(v.variantId) ?? "stable"})`
309
569
  ).join("\n");
310
- return { content: [{ type: "text", text }], structuredContent };
570
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
311
571
  }
312
572
  );
313
573
  }
@@ -387,6 +647,7 @@ function registerPersonaTools(server, client) {
387
647
  title: "Persona breakdown",
388
648
  description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
389
649
  inputSchema: { projectId: projectIdSchema4 },
650
+ _meta: uiMeta("persona-breakdown"),
390
651
  outputSchema: {
391
652
  totalSessions: z4.number().describe("Total sessions across all clusters"),
392
653
  clusters: z4.array(
@@ -419,7 +680,8 @@ function registerPersonaTools(server, client) {
419
680
  if (!data.clusters.length) {
420
681
  return {
421
682
  content: [{ type: "text", text: "No persona clusters yet. More visitor data is needed." }],
422
- structuredContent
683
+ structuredContent,
684
+ _meta: uiMeta("persona-breakdown")
423
685
  };
424
686
  }
425
687
  const lines = [
@@ -431,7 +693,7 @@ function registerPersonaTools(server, client) {
431
693
  return `- ${c.label}: ${c.sessionCount} sessions (${pct.toFixed(1)}% of traffic, reliability ${(c.avgReliability * 100).toFixed(0)}%)`;
432
694
  })
433
695
  ];
434
- return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
696
+ return { content: [{ type: "text", text: lines.join("\n") }], structuredContent, _meta: uiMeta("persona-breakdown") };
435
697
  }
436
698
  );
437
699
  }
@@ -446,6 +708,7 @@ function registerGoalTools(server, client) {
446
708
  title: "Goal funnel",
447
709
  description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
448
710
  inputSchema: { projectId: projectIdSchema5 },
711
+ _meta: uiMeta("goal-funnel"),
449
712
  outputSchema: {
450
713
  goals: z5.array(
451
714
  z5.object({
@@ -488,7 +751,8 @@ function registerGoalTools(server, client) {
488
751
  if (!data.goals.length) {
489
752
  return {
490
753
  content: [{ type: "text", text: "No goals configured for this project." }],
491
- structuredContent
754
+ structuredContent,
755
+ _meta: uiMeta("goal-funnel")
492
756
  };
493
757
  }
494
758
  const lines = data.goals.flatMap((g) => [
@@ -496,7 +760,7 @@ function registerGoalTools(server, client) {
496
760
  ...g.variants.map((v) => ` ${v.componentId}/${v.variantId}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
497
761
  ""
498
762
  ]);
499
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
763
+ return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
500
764
  }
501
765
  );
502
766
  }
@@ -560,6 +824,7 @@ function registerLayoutTools(server, client) {
560
824
  title: "Layout stats",
561
825
  description: "Get per-persona section layout rankings and bandit reward weights.",
562
826
  inputSchema: { projectId: projectIdSchema7 },
827
+ _meta: uiMeta("layout-stats"),
563
828
  outputSchema: {
564
829
  layouts: z7.array(
565
830
  z7.object({
@@ -590,13 +855,14 @@ function registerLayoutTools(server, client) {
590
855
  if (!stats.length) {
591
856
  return {
592
857
  content: [{ type: "text", text: "No layout data yet. More visitor sessions are needed." }],
593
- structuredContent
858
+ structuredContent,
859
+ _meta: uiMeta("layout-stats")
594
860
  };
595
861
  }
596
862
  const text = stats.map(
597
863
  (s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
598
864
  ).join("\n");
599
- return { content: [{ type: "text", text }], structuredContent };
865
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("layout-stats") };
600
866
  }
601
867
  );
602
868
  }
@@ -1113,10 +1379,26 @@ function registerIntegrationGuideTools(server) {
1113
1379
  // src/server.ts
1114
1380
  var { version: PKG_VERSION } = createRequire(import.meta.url)("../package.json");
1115
1381
  function createMcpServer(client) {
1116
- const server = new McpServer({
1117
- name: "@sentientui/mcp",
1118
- version: PKG_VERSION
1119
- });
1382
+ const server = new McpServer(
1383
+ {
1384
+ name: "@sentientui/mcp",
1385
+ version: PKG_VERSION
1386
+ },
1387
+ {
1388
+ // MCP Apps (SEP-1865): advertise the `io.modelcontextprotocol/ui` extension
1389
+ // in the initialize handshake so UI-capable hosts and agent-readiness
1390
+ // scanners negotiate it. Merged with the tools/resources capabilities the
1391
+ // SDK adds as they're registered below.
1392
+ capabilities: {
1393
+ extensions: {
1394
+ "io.modelcontextprotocol/ui": {
1395
+ mimeTypes: [RESOURCE_MIME_TYPE]
1396
+ }
1397
+ }
1398
+ }
1399
+ }
1400
+ );
1401
+ registerUiResources(server);
1120
1402
  registerProjectTools(server, client);
1121
1403
  registerComponentTools(server, client);
1122
1404
  registerInsightTools(server, client);
package/dist/index.cjs CHANGED
@@ -223,6 +223,264 @@ 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 RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
441
+ var UI_TOOL_VIZ = {
442
+ get_persona_breakdown: "persona-breakdown",
443
+ get_variant_performance: "variant-performance",
444
+ get_goal_funnel: "goal-funnel",
445
+ get_layout_stats: "layout-stats"
446
+ };
447
+ function uiResourceUri(id) {
448
+ return `ui://sentientui/${id}`;
449
+ }
450
+ function uiMeta(id) {
451
+ const uri = uiResourceUri(id);
452
+ return {
453
+ ui: {
454
+ resourceUri: uri,
455
+ preferredFrameSize: ["720px", "480px"]
456
+ },
457
+ "openai/outputTemplate": uri
458
+ };
459
+ }
460
+ function registerUiResources(server) {
461
+ for (const id of Object.values(UI_TOOL_VIZ)) {
462
+ server.registerResource(
463
+ id,
464
+ uiResourceUri(id),
465
+ {
466
+ title: `${VIZ_TITLES[id]} (UI)`,
467
+ description: `Interactive ${VIZ_TITLES[id]} view for agent hosts that support MCP UI.`,
468
+ mimeType: RESOURCE_MIME_TYPE
469
+ },
470
+ async (uri) => ({
471
+ contents: [
472
+ {
473
+ uri: uri.href,
474
+ mimeType: RESOURCE_MIME_TYPE,
475
+ text: buildTemplate(id)
476
+ }
477
+ ]
478
+ })
479
+ );
480
+ }
481
+ }
482
+
483
+ // src/tools/components.ts
226
484
  var projectIdSchema2 = import_zod2.z.string().uuid().describe("The project UUID");
227
485
  function registerComponentTools(server, client) {
228
486
  server.registerTool(
@@ -276,6 +534,7 @@ function registerComponentTools(server, client) {
276
534
  title: "Variant performance",
277
535
  description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
278
536
  inputSchema: { projectId: projectIdSchema2 },
537
+ _meta: uiMeta("variant-performance"),
279
538
  outputSchema: {
280
539
  variants: import_zod2.z.array(
281
540
  import_zod2.z.object({
@@ -309,13 +568,14 @@ function registerComponentTools(server, client) {
309
568
  if (!data.cvr?.length) {
310
569
  return {
311
570
  content: [{ type: "text", text: "No variant data available yet." }],
312
- structuredContent
571
+ structuredContent,
572
+ _meta: uiMeta("variant-performance")
313
573
  };
314
574
  }
315
575
  const text = data.cvr.map(
316
576
  (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
577
  ).join("\n");
318
- return { content: [{ type: "text", text }], structuredContent };
578
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
319
579
  }
320
580
  );
321
581
  }
@@ -395,6 +655,7 @@ function registerPersonaTools(server, client) {
395
655
  title: "Persona breakdown",
396
656
  description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
397
657
  inputSchema: { projectId: projectIdSchema4 },
658
+ _meta: uiMeta("persona-breakdown"),
398
659
  outputSchema: {
399
660
  totalSessions: import_zod4.z.number().describe("Total sessions across all clusters"),
400
661
  clusters: import_zod4.z.array(
@@ -427,7 +688,8 @@ function registerPersonaTools(server, client) {
427
688
  if (!data.clusters.length) {
428
689
  return {
429
690
  content: [{ type: "text", text: "No persona clusters yet. More visitor data is needed." }],
430
- structuredContent
691
+ structuredContent,
692
+ _meta: uiMeta("persona-breakdown")
431
693
  };
432
694
  }
433
695
  const lines = [
@@ -439,7 +701,7 @@ function registerPersonaTools(server, client) {
439
701
  return `- ${c.label}: ${c.sessionCount} sessions (${pct.toFixed(1)}% of traffic, reliability ${(c.avgReliability * 100).toFixed(0)}%)`;
440
702
  })
441
703
  ];
442
- return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
704
+ return { content: [{ type: "text", text: lines.join("\n") }], structuredContent, _meta: uiMeta("persona-breakdown") };
443
705
  }
444
706
  );
445
707
  }
@@ -454,6 +716,7 @@ function registerGoalTools(server, client) {
454
716
  title: "Goal funnel",
455
717
  description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
456
718
  inputSchema: { projectId: projectIdSchema5 },
719
+ _meta: uiMeta("goal-funnel"),
457
720
  outputSchema: {
458
721
  goals: import_zod5.z.array(
459
722
  import_zod5.z.object({
@@ -496,7 +759,8 @@ function registerGoalTools(server, client) {
496
759
  if (!data.goals.length) {
497
760
  return {
498
761
  content: [{ type: "text", text: "No goals configured for this project." }],
499
- structuredContent
762
+ structuredContent,
763
+ _meta: uiMeta("goal-funnel")
500
764
  };
501
765
  }
502
766
  const lines = data.goals.flatMap((g) => [
@@ -504,7 +768,7 @@ function registerGoalTools(server, client) {
504
768
  ...g.variants.map((v) => ` ${v.componentId}/${v.variantId}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
505
769
  ""
506
770
  ]);
507
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
771
+ return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
508
772
  }
509
773
  );
510
774
  }
@@ -568,6 +832,7 @@ function registerLayoutTools(server, client) {
568
832
  title: "Layout stats",
569
833
  description: "Get per-persona section layout rankings and bandit reward weights.",
570
834
  inputSchema: { projectId: projectIdSchema7 },
835
+ _meta: uiMeta("layout-stats"),
571
836
  outputSchema: {
572
837
  layouts: import_zod7.z.array(
573
838
  import_zod7.z.object({
@@ -598,13 +863,14 @@ function registerLayoutTools(server, client) {
598
863
  if (!stats.length) {
599
864
  return {
600
865
  content: [{ type: "text", text: "No layout data yet. More visitor sessions are needed." }],
601
- structuredContent
866
+ structuredContent,
867
+ _meta: uiMeta("layout-stats")
602
868
  };
603
869
  }
604
870
  const text = stats.map(
605
871
  (s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
606
872
  ).join("\n");
607
- return { content: [{ type: "text", text }], structuredContent };
873
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("layout-stats") };
608
874
  }
609
875
  );
610
876
  }
@@ -1121,10 +1387,26 @@ function registerIntegrationGuideTools(server) {
1121
1387
  // src/server.ts
1122
1388
  var { version: PKG_VERSION } = (0, import_node_module.createRequire)(importMetaUrl)("../package.json");
1123
1389
  function createMcpServer(client) {
1124
- const server = new import_mcp.McpServer({
1125
- name: "@sentientui/mcp",
1126
- version: PKG_VERSION
1127
- });
1390
+ const server = new import_mcp.McpServer(
1391
+ {
1392
+ name: "@sentientui/mcp",
1393
+ version: PKG_VERSION
1394
+ },
1395
+ {
1396
+ // MCP Apps (SEP-1865): advertise the `io.modelcontextprotocol/ui` extension
1397
+ // in the initialize handshake so UI-capable hosts and agent-readiness
1398
+ // scanners negotiate it. Merged with the tools/resources capabilities the
1399
+ // SDK adds as they're registered below.
1400
+ capabilities: {
1401
+ extensions: {
1402
+ "io.modelcontextprotocol/ui": {
1403
+ mimeTypes: [RESOURCE_MIME_TYPE]
1404
+ }
1405
+ }
1406
+ }
1407
+ }
1408
+ );
1409
+ registerUiResources(server);
1128
1410
  registerProjectTools(server, client);
1129
1411
  registerComponentTools(server, client);
1130
1412
  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-OQ2K3O7A.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,264 @@ 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 RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
466
+ var UI_TOOL_VIZ = {
467
+ get_persona_breakdown: "persona-breakdown",
468
+ get_variant_performance: "variant-performance",
469
+ get_goal_funnel: "goal-funnel",
470
+ get_layout_stats: "layout-stats"
471
+ };
472
+ function uiResourceUri(id) {
473
+ return `ui://sentientui/${id}`;
474
+ }
475
+ function uiMeta(id) {
476
+ const uri = uiResourceUri(id);
477
+ return {
478
+ ui: {
479
+ resourceUri: uri,
480
+ preferredFrameSize: ["720px", "480px"]
481
+ },
482
+ "openai/outputTemplate": uri
483
+ };
484
+ }
485
+ function registerUiResources(server) {
486
+ for (const id of Object.values(UI_TOOL_VIZ)) {
487
+ server.registerResource(
488
+ id,
489
+ uiResourceUri(id),
490
+ {
491
+ title: `${VIZ_TITLES[id]} (UI)`,
492
+ description: `Interactive ${VIZ_TITLES[id]} view for agent hosts that support MCP UI.`,
493
+ mimeType: RESOURCE_MIME_TYPE
494
+ },
495
+ async (uri) => ({
496
+ contents: [
497
+ {
498
+ uri: uri.href,
499
+ mimeType: RESOURCE_MIME_TYPE,
500
+ text: buildTemplate(id)
501
+ }
502
+ ]
503
+ })
504
+ );
505
+ }
506
+ }
507
+
508
+ // src/tools/components.ts
251
509
  var projectIdSchema2 = import_zod2.z.string().uuid().describe("The project UUID");
252
510
  function registerComponentTools(server, client) {
253
511
  server.registerTool(
@@ -301,6 +559,7 @@ function registerComponentTools(server, client) {
301
559
  title: "Variant performance",
302
560
  description: "Get CVR and momentum for all variants in a project over the last 7 days vs prior 7 days.",
303
561
  inputSchema: { projectId: projectIdSchema2 },
562
+ _meta: uiMeta("variant-performance"),
304
563
  outputSchema: {
305
564
  variants: import_zod2.z.array(
306
565
  import_zod2.z.object({
@@ -334,13 +593,14 @@ function registerComponentTools(server, client) {
334
593
  if (!data.cvr?.length) {
335
594
  return {
336
595
  content: [{ type: "text", text: "No variant data available yet." }],
337
- structuredContent
596
+ structuredContent,
597
+ _meta: uiMeta("variant-performance")
338
598
  };
339
599
  }
340
600
  const text = data.cvr.map(
341
601
  (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
602
  ).join("\n");
343
- return { content: [{ type: "text", text }], structuredContent };
603
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("variant-performance") };
344
604
  }
345
605
  );
346
606
  }
@@ -420,6 +680,7 @@ function registerPersonaTools(server, client) {
420
680
  title: "Persona breakdown",
421
681
  description: "Get the distribution of visitor persona clusters with session counts and reliability scores.",
422
682
  inputSchema: { projectId: projectIdSchema4 },
683
+ _meta: uiMeta("persona-breakdown"),
423
684
  outputSchema: {
424
685
  totalSessions: import_zod4.z.number().describe("Total sessions across all clusters"),
425
686
  clusters: import_zod4.z.array(
@@ -452,7 +713,8 @@ function registerPersonaTools(server, client) {
452
713
  if (!data.clusters.length) {
453
714
  return {
454
715
  content: [{ type: "text", text: "No persona clusters yet. More visitor data is needed." }],
455
- structuredContent
716
+ structuredContent,
717
+ _meta: uiMeta("persona-breakdown")
456
718
  };
457
719
  }
458
720
  const lines = [
@@ -464,7 +726,7 @@ function registerPersonaTools(server, client) {
464
726
  return `- ${c.label}: ${c.sessionCount} sessions (${pct.toFixed(1)}% of traffic, reliability ${(c.avgReliability * 100).toFixed(0)}%)`;
465
727
  })
466
728
  ];
467
- return { content: [{ type: "text", text: lines.join("\n") }], structuredContent };
729
+ return { content: [{ type: "text", text: lines.join("\n") }], structuredContent, _meta: uiMeta("persona-breakdown") };
468
730
  }
469
731
  );
470
732
  }
@@ -479,6 +741,7 @@ function registerGoalTools(server, client) {
479
741
  title: "Goal funnel",
480
742
  description: "Get goal hit counts, unique-session conversion rates, and per-variant breakdown.",
481
743
  inputSchema: { projectId: projectIdSchema5 },
744
+ _meta: uiMeta("goal-funnel"),
482
745
  outputSchema: {
483
746
  goals: import_zod5.z.array(
484
747
  import_zod5.z.object({
@@ -521,7 +784,8 @@ function registerGoalTools(server, client) {
521
784
  if (!data.goals.length) {
522
785
  return {
523
786
  content: [{ type: "text", text: "No goals configured for this project." }],
524
- structuredContent
787
+ structuredContent,
788
+ _meta: uiMeta("goal-funnel")
525
789
  };
526
790
  }
527
791
  const lines = data.goals.flatMap((g) => [
@@ -529,7 +793,7 @@ function registerGoalTools(server, client) {
529
793
  ...g.variants.map((v) => ` ${v.componentId}/${v.variantId}: ${(v.completionRate * 100).toFixed(1)}% per assigned session`),
530
794
  ""
531
795
  ]);
532
- return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent };
796
+ return { content: [{ type: "text", text: lines.join("\n").trim() }], structuredContent, _meta: uiMeta("goal-funnel") };
533
797
  }
534
798
  );
535
799
  }
@@ -593,6 +857,7 @@ function registerLayoutTools(server, client) {
593
857
  title: "Layout stats",
594
858
  description: "Get per-persona section layout rankings and bandit reward weights.",
595
859
  inputSchema: { projectId: projectIdSchema7 },
860
+ _meta: uiMeta("layout-stats"),
596
861
  outputSchema: {
597
862
  layouts: import_zod7.z.array(
598
863
  import_zod7.z.object({
@@ -623,13 +888,14 @@ function registerLayoutTools(server, client) {
623
888
  if (!stats.length) {
624
889
  return {
625
890
  content: [{ type: "text", text: "No layout data yet. More visitor sessions are needed." }],
626
- structuredContent
891
+ structuredContent,
892
+ _meta: uiMeta("layout-stats")
627
893
  };
628
894
  }
629
895
  const text = stats.map(
630
896
  (s) => `- ${s.persona}: [${s.layoutOrder.join(" \u2192 ")}] (avg reward: ${s.avgReward.toFixed(2)}, ${s.pulls} pulls)`
631
897
  ).join("\n");
632
- return { content: [{ type: "text", text }], structuredContent };
898
+ return { content: [{ type: "text", text }], structuredContent, _meta: uiMeta("layout-stats") };
633
899
  }
634
900
  );
635
901
  }
@@ -1146,10 +1412,26 @@ function registerIntegrationGuideTools(server) {
1146
1412
  // src/server.ts
1147
1413
  var { version: PKG_VERSION } = (0, import_node_module.createRequire)(importMetaUrl)("../package.json");
1148
1414
  function createMcpServer(client) {
1149
- const server = new import_mcp.McpServer({
1150
- name: "@sentientui/mcp",
1151
- version: PKG_VERSION
1152
- });
1415
+ const server = new import_mcp.McpServer(
1416
+ {
1417
+ name: "@sentientui/mcp",
1418
+ version: PKG_VERSION
1419
+ },
1420
+ {
1421
+ // MCP Apps (SEP-1865): advertise the `io.modelcontextprotocol/ui` extension
1422
+ // in the initialize handshake so UI-capable hosts and agent-readiness
1423
+ // scanners negotiate it. Merged with the tools/resources capabilities the
1424
+ // SDK adds as they're registered below.
1425
+ capabilities: {
1426
+ extensions: {
1427
+ "io.modelcontextprotocol/ui": {
1428
+ mimeTypes: [RESOURCE_MIME_TYPE]
1429
+ }
1430
+ }
1431
+ }
1432
+ }
1433
+ );
1434
+ registerUiResources(server);
1153
1435
  registerProjectTools(server, client);
1154
1436
  registerComponentTools(server, client);
1155
1437
  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-OQ2K3O7A.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.1",
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",