@sentientui/mcp 0.17.7 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -43,6 +43,9 @@ Server keys start with `sk_` — required for the management API. Public keys (`
43
43
  | `get_variant_brief` | **Start here to optimize a component.** Insight-driven brief for writing a new CODE-NATIVE variant: performance, audience, insights, data-sufficiency + best-practice fallback, and code instructions |
44
44
  | `create_variant` | Creates a no-code **managed text** DRAFT — user must activate in dashboard. Fallback only; not for code-native variants |
45
45
  | `pause_variant` | Stops traffic; no MCP resume |
46
+ | `get_cell_matrix` | "Who sees what": per-(visitor type, region) generation state + traffic share. A missing cell = they see the original |
47
+ | `get_cell_detail` | One cell's generated option, its stored rationale, and a plain-language performance verdict |
48
+ | `generate_cell` | Queues async AI generation for one cell (also retries failed ones). Paid plan; check `get_cell_matrix` for progress |
46
49
 
47
50
  ## Canonical Workflows
48
51
 
package/README.md CHANGED
@@ -101,6 +101,9 @@ A sandboxed demo token is provisioned automatically — 10 calls/month, read-onl
101
101
  | `get_variant_brief` | Insight-driven brief for writing a new **code-native** variant: performance, audience, insights, data-sufficiency (with a best-practice fallback when there's no data yet), and step-by-step code instructions |
102
102
  | `create_variant` | Create a no-code (managed) text variant (Starter+) — fallback for text-only variants without a code change |
103
103
  | `pause_variant` | Pause a variant to stop traffic assignment |
104
+ | `get_cell_matrix` | The "Who sees what" matrix: which visitor types have an AI-generated version live in each personalizable region, with traffic share and auto-fill status |
105
+ | `get_cell_detail` | One cell's story: the generated option, why it was written (stored rationale), and how it performs vs the original |
106
+ | `generate_cell` | Queue AI generation of a version for one (visitor type, region) cell — brand-locked, live in under a minute (paid plan) |
104
107
 
105
108
  > **Code-native vs no-code variants.** Variants you declare in your app (`<Adaptive variants={{…}}>`) register automatically the first time the SDK requests an assignment after you deploy — they go live immediately and need **no** `create_variant` call. Use `create_variant` only for **no-code** variants whose content is stored in SentientUI and rendered without a code change; these start as drafts you activate from the dashboard.
106
109
  >
@@ -1856,6 +1856,15 @@ useLayoutOrder(). Non-React sites: sections: ['#hero', '#pricing', '#faq'] (CSS
1856
1856
  the theme's natural order) in window.sentient \u2014 the snippet reorders those elements within
1857
1857
  their shared parent, and applies nothing if any selector drifts.
1858
1858
 
1859
+ ## No-code install order (snippet)
1860
+
1861
+ Three tags in <head>, in this order: the window.sentient config, the inline pre-paint script,
1862
+ then the deferred loader. The middle tag is optional but recommended \u2014 the loader is deferred,
1863
+ so on a return visit the page can paint its original state before the loader runs; the inline
1864
+ script applies the last served decision from the visitor's own device first, with no network
1865
+ call and no cloak. Get its exact bytes from the dashboard's Install page, or from
1866
+ renderSnippetPrePaintScript() in '@sentientui/snippet/install'.
1867
+
1859
1868
  ## Testing the integration
1860
1869
 
1861
1870
  Use '@sentientui/react/testing': renderWithSentient(ui, { variants, slots, persona }) forces
@@ -2017,8 +2026,158 @@ function registerAgentTrafficTools(server, client) {
2017
2026
  );
2018
2027
  }
2019
2028
 
2029
+ // src/tools/cells.ts
2030
+ import { z as z15 } from "zod";
2031
+ var personaOutput = z15.object({
2032
+ key: z15.string(),
2033
+ displayName: z15.string(),
2034
+ description: z15.string().nullable(),
2035
+ share: z15.number().describe("Fraction of 30-day decided sessions")
2036
+ });
2037
+ var cellOutput = z15.object({
2038
+ slotId: z15.string(),
2039
+ persona: z15.string(),
2040
+ status: z15.string().describe("pending | generating | live | error | rejected"),
2041
+ requestedBy: z15.string(),
2042
+ armId: z15.string().nullable(),
2043
+ rationale: z15.string().nullable(),
2044
+ error: z15.string().nullable(),
2045
+ updatedAt: z15.string(),
2046
+ performance: z15.object({
2047
+ shown: z15.number(),
2048
+ conversions: z15.number(),
2049
+ deltaPct: z15.number().nullable().describe("Signed % vs the original conversion rate"),
2050
+ tier: z15.string().describe("early | no_baseline | winning | losing | flat")
2051
+ }).optional().describe("Present on live cells: the at-a-glance readout")
2052
+ });
2053
+ function registerCellTools(server, client) {
2054
+ server.registerTool(
2055
+ "get_cell_matrix",
2056
+ {
2057
+ title: "Persona \xD7 slot cell matrix",
2058
+ description: 'The "Who sees what" matrix: which visitor types (personas) have an AI-generated version live in each personalizable region (slot), with per-persona traffic share and the first-miss auto-fill setting. A cell with no row means that visitor type sees the original.' + UNTRUSTED_FIELDS_NOTE,
2059
+ inputSchema: { projectId: projectIdSchema },
2060
+ outputSchema: {
2061
+ slots: z15.array(z15.object({ slotId: z15.string(), displayName: z15.string(), supportsForms: z15.boolean() })),
2062
+ personas: z15.array(personaOutput),
2063
+ cells: z15.array(cellOutput),
2064
+ autoFill: z15.boolean(),
2065
+ personaSource: z15.string().describe("Active persona-set source: default | declared | discovered"),
2066
+ undecidedShare: z15.number()
2067
+ },
2068
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
2069
+ },
2070
+ withApiErrorGuidance(async ({ projectId }) => {
2071
+ const id = encodeURIComponent(projectId);
2072
+ const data = await client.get(`/projects/${id}/cells`);
2073
+ const byCell = new Map(data.cells.map((c) => [`${c.slotId}::${c.persona}`, c]));
2074
+ const lines = [
2075
+ `Slots: ${data.slots.map((s) => untrusted(s.displayName)).join(", ") || "(none published)"}`,
2076
+ `Auto-fill on first miss: ${data.autoFill ? "ON" : "off"} \xB7 persona source: ${data.personaSource}`
2077
+ ];
2078
+ for (const p of data.personas) {
2079
+ const states = data.slots.map((s) => {
2080
+ const c = byCell.get(`${s.slotId}::${p.key}`);
2081
+ return `${untrusted(s.displayName, 40)}: ${c ? c.status : "original"}`;
2082
+ });
2083
+ lines.push(`- ${untrusted(p.displayName)} (${Math.round(p.share * 100)}% of visitors) \u2014 ${states.join(" \xB7 ")}`);
2084
+ }
2085
+ lines.push(`Undecided visitors (${Math.round(data.undecidedShare * 100)}%) always see the original.`);
2086
+ return {
2087
+ content: [{ type: "text", text: lines.join("\n") }],
2088
+ structuredContent: data
2089
+ };
2090
+ })
2091
+ );
2092
+ server.registerTool(
2093
+ "get_cell_detail",
2094
+ {
2095
+ title: "Cell story",
2096
+ description: "One cell's full story: the generated option a visitor type sees in a slot, why it was written (the stored generation rationale), and how it is performing vs the original (deterministic plain-language verdict)." + UNTRUSTED_FIELDS_NOTE,
2097
+ inputSchema: {
2098
+ projectId: projectIdSchema,
2099
+ slotId: z15.string().min(1).max(128).describe("The slot id (a matrix column)"),
2100
+ persona: z15.string().min(1).max(64).describe("The persona key (a matrix row)")
2101
+ },
2102
+ outputSchema: {
2103
+ cell: cellOutput.nullable(),
2104
+ arm: z15.object({
2105
+ id: z15.string(),
2106
+ displayName: z15.string().nullable(),
2107
+ content: z15.string().nullable(),
2108
+ blocks: z15.unknown().nullable()
2109
+ }).nullable(),
2110
+ performance: z15.object({
2111
+ shown: z15.number(),
2112
+ conversions: z15.number(),
2113
+ conversionRate: z15.number(),
2114
+ baselineShown: z15.number(),
2115
+ baselineConversions: z15.number(),
2116
+ baselineConversionRate: z15.number()
2117
+ }).nullable(),
2118
+ verdict: z15.string()
2119
+ },
2120
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
2121
+ },
2122
+ withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
2123
+ var _a, _b;
2124
+ const id = encodeURIComponent(projectId);
2125
+ const data = await client.get(`/projects/${id}/cells/detail?slotId=${encodeURIComponent(slotId)}&persona=${encodeURIComponent(persona)}`);
2126
+ const lines = [];
2127
+ if (data.arm) {
2128
+ lines.push(
2129
+ data.arm.content !== null ? `They see: "${untrusted(data.arm.content, 300)}"` : `They see a designed layout (option ${untrusted(data.arm.id, 64)}).`
2130
+ );
2131
+ } else {
2132
+ lines.push("No generated version is live for this cell.");
2133
+ }
2134
+ if ((_a = data.cell) == null ? void 0 : _a.rationale) lines.push(`Why this exists: ${untrusted(data.cell.rationale, 500)}`);
2135
+ if ((_b = data.cell) == null ? void 0 : _b.error) lines.push(`Last attempt failed: ${untrusted(data.cell.error, 300)}`);
2136
+ lines.push(data.verdict);
2137
+ if (data.performance) {
2138
+ lines.push(`Shown ${data.performance.shown} times, ${data.performance.conversions} conversions.`);
2139
+ }
2140
+ return {
2141
+ content: [{ type: "text", text: lines.join("\n") }],
2142
+ structuredContent: data
2143
+ };
2144
+ })
2145
+ );
2146
+ server.registerTool(
2147
+ "generate_cell",
2148
+ {
2149
+ title: "Generate a cell version",
2150
+ description: "Queue AI generation of a version for one (persona, slot) cell. The worker writes it asynchronously (usually under a minute), brand-locked, and it goes live as a named option the optimizer explores. Also works to regenerate or retry a failed cell. Requires a paid plan (anonymous demo tokens are read-only).",
2151
+ inputSchema: {
2152
+ projectId: projectIdSchema,
2153
+ slotId: z15.string().min(1).max(128).describe("The slot id (a matrix column)"),
2154
+ persona: z15.string().min(1).max(64).describe("The persona key (a matrix row)")
2155
+ },
2156
+ outputSchema: {
2157
+ slotId: z15.string(),
2158
+ persona: z15.string(),
2159
+ status: z15.literal("pending").describe("Queued \u2014 check get_cell_matrix for progress")
2160
+ },
2161
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2162
+ },
2163
+ withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
2164
+ const id = encodeURIComponent(projectId);
2165
+ await client.post(`/projects/${id}/cells/generate`, { slotId, persona });
2166
+ return {
2167
+ content: [
2168
+ {
2169
+ type: "text",
2170
+ text: `Writing a version of ${untrusted(slotId, 64)} for ${untrusted(persona, 64)} \u2014 it usually goes live in under a minute. Check get_cell_matrix (status flips pending \u2192 live) or get_cell_detail for the story once it lands.`
2171
+ }
2172
+ ],
2173
+ structuredContent: { slotId, persona, status: "pending" }
2174
+ };
2175
+ })
2176
+ );
2177
+ }
2178
+
2020
2179
  // src/server.ts
2021
- var PKG_VERSION = true ? "0.17.7" : "0.0.0-dev";
2180
+ var PKG_VERSION = true ? "0.18.0" : "0.0.0-dev";
2022
2181
  function createMcpServer(client) {
2023
2182
  const server = new McpServer(
2024
2183
  {
@@ -2053,6 +2212,7 @@ function createMcpServer(client) {
2053
2212
  registerVariantWriteTools(server, client);
2054
2213
  registerIntegrationGuideTools(server);
2055
2214
  registerAgentTrafficTools(server, client);
2215
+ registerCellTools(server, client);
2056
2216
  return server;
2057
2217
  }
2058
2218
 
package/dist/index.cjs CHANGED
@@ -1862,6 +1862,15 @@ useLayoutOrder(). Non-React sites: sections: ['#hero', '#pricing', '#faq'] (CSS
1862
1862
  the theme's natural order) in window.sentient \u2014 the snippet reorders those elements within
1863
1863
  their shared parent, and applies nothing if any selector drifts.
1864
1864
 
1865
+ ## No-code install order (snippet)
1866
+
1867
+ Three tags in <head>, in this order: the window.sentient config, the inline pre-paint script,
1868
+ then the deferred loader. The middle tag is optional but recommended \u2014 the loader is deferred,
1869
+ so on a return visit the page can paint its original state before the loader runs; the inline
1870
+ script applies the last served decision from the visitor's own device first, with no network
1871
+ call and no cloak. Get its exact bytes from the dashboard's Install page, or from
1872
+ renderSnippetPrePaintScript() in '@sentientui/snippet/install'.
1873
+
1865
1874
  ## Testing the integration
1866
1875
 
1867
1876
  Use '@sentientui/react/testing': renderWithSentient(ui, { variants, slots, persona }) forces
@@ -2023,8 +2032,158 @@ function registerAgentTrafficTools(server, client) {
2023
2032
  );
2024
2033
  }
2025
2034
 
2035
+ // src/tools/cells.ts
2036
+ var import_zod15 = require("zod");
2037
+ var personaOutput = import_zod15.z.object({
2038
+ key: import_zod15.z.string(),
2039
+ displayName: import_zod15.z.string(),
2040
+ description: import_zod15.z.string().nullable(),
2041
+ share: import_zod15.z.number().describe("Fraction of 30-day decided sessions")
2042
+ });
2043
+ var cellOutput = import_zod15.z.object({
2044
+ slotId: import_zod15.z.string(),
2045
+ persona: import_zod15.z.string(),
2046
+ status: import_zod15.z.string().describe("pending | generating | live | error | rejected"),
2047
+ requestedBy: import_zod15.z.string(),
2048
+ armId: import_zod15.z.string().nullable(),
2049
+ rationale: import_zod15.z.string().nullable(),
2050
+ error: import_zod15.z.string().nullable(),
2051
+ updatedAt: import_zod15.z.string(),
2052
+ performance: import_zod15.z.object({
2053
+ shown: import_zod15.z.number(),
2054
+ conversions: import_zod15.z.number(),
2055
+ deltaPct: import_zod15.z.number().nullable().describe("Signed % vs the original conversion rate"),
2056
+ tier: import_zod15.z.string().describe("early | no_baseline | winning | losing | flat")
2057
+ }).optional().describe("Present on live cells: the at-a-glance readout")
2058
+ });
2059
+ function registerCellTools(server, client) {
2060
+ server.registerTool(
2061
+ "get_cell_matrix",
2062
+ {
2063
+ title: "Persona \xD7 slot cell matrix",
2064
+ description: 'The "Who sees what" matrix: which visitor types (personas) have an AI-generated version live in each personalizable region (slot), with per-persona traffic share and the first-miss auto-fill setting. A cell with no row means that visitor type sees the original.' + UNTRUSTED_FIELDS_NOTE,
2065
+ inputSchema: { projectId: projectIdSchema },
2066
+ outputSchema: {
2067
+ slots: import_zod15.z.array(import_zod15.z.object({ slotId: import_zod15.z.string(), displayName: import_zod15.z.string(), supportsForms: import_zod15.z.boolean() })),
2068
+ personas: import_zod15.z.array(personaOutput),
2069
+ cells: import_zod15.z.array(cellOutput),
2070
+ autoFill: import_zod15.z.boolean(),
2071
+ personaSource: import_zod15.z.string().describe("Active persona-set source: default | declared | discovered"),
2072
+ undecidedShare: import_zod15.z.number()
2073
+ },
2074
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
2075
+ },
2076
+ withApiErrorGuidance(async ({ projectId }) => {
2077
+ const id = encodeURIComponent(projectId);
2078
+ const data = await client.get(`/projects/${id}/cells`);
2079
+ const byCell = new Map(data.cells.map((c) => [`${c.slotId}::${c.persona}`, c]));
2080
+ const lines = [
2081
+ `Slots: ${data.slots.map((s) => untrusted(s.displayName)).join(", ") || "(none published)"}`,
2082
+ `Auto-fill on first miss: ${data.autoFill ? "ON" : "off"} \xB7 persona source: ${data.personaSource}`
2083
+ ];
2084
+ for (const p of data.personas) {
2085
+ const states = data.slots.map((s) => {
2086
+ const c = byCell.get(`${s.slotId}::${p.key}`);
2087
+ return `${untrusted(s.displayName, 40)}: ${c ? c.status : "original"}`;
2088
+ });
2089
+ lines.push(`- ${untrusted(p.displayName)} (${Math.round(p.share * 100)}% of visitors) \u2014 ${states.join(" \xB7 ")}`);
2090
+ }
2091
+ lines.push(`Undecided visitors (${Math.round(data.undecidedShare * 100)}%) always see the original.`);
2092
+ return {
2093
+ content: [{ type: "text", text: lines.join("\n") }],
2094
+ structuredContent: data
2095
+ };
2096
+ })
2097
+ );
2098
+ server.registerTool(
2099
+ "get_cell_detail",
2100
+ {
2101
+ title: "Cell story",
2102
+ description: "One cell's full story: the generated option a visitor type sees in a slot, why it was written (the stored generation rationale), and how it is performing vs the original (deterministic plain-language verdict)." + UNTRUSTED_FIELDS_NOTE,
2103
+ inputSchema: {
2104
+ projectId: projectIdSchema,
2105
+ slotId: import_zod15.z.string().min(1).max(128).describe("The slot id (a matrix column)"),
2106
+ persona: import_zod15.z.string().min(1).max(64).describe("The persona key (a matrix row)")
2107
+ },
2108
+ outputSchema: {
2109
+ cell: cellOutput.nullable(),
2110
+ arm: import_zod15.z.object({
2111
+ id: import_zod15.z.string(),
2112
+ displayName: import_zod15.z.string().nullable(),
2113
+ content: import_zod15.z.string().nullable(),
2114
+ blocks: import_zod15.z.unknown().nullable()
2115
+ }).nullable(),
2116
+ performance: import_zod15.z.object({
2117
+ shown: import_zod15.z.number(),
2118
+ conversions: import_zod15.z.number(),
2119
+ conversionRate: import_zod15.z.number(),
2120
+ baselineShown: import_zod15.z.number(),
2121
+ baselineConversions: import_zod15.z.number(),
2122
+ baselineConversionRate: import_zod15.z.number()
2123
+ }).nullable(),
2124
+ verdict: import_zod15.z.string()
2125
+ },
2126
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
2127
+ },
2128
+ withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
2129
+ var _a2, _b;
2130
+ const id = encodeURIComponent(projectId);
2131
+ const data = await client.get(`/projects/${id}/cells/detail?slotId=${encodeURIComponent(slotId)}&persona=${encodeURIComponent(persona)}`);
2132
+ const lines = [];
2133
+ if (data.arm) {
2134
+ lines.push(
2135
+ data.arm.content !== null ? `They see: "${untrusted(data.arm.content, 300)}"` : `They see a designed layout (option ${untrusted(data.arm.id, 64)}).`
2136
+ );
2137
+ } else {
2138
+ lines.push("No generated version is live for this cell.");
2139
+ }
2140
+ if ((_a2 = data.cell) == null ? void 0 : _a2.rationale) lines.push(`Why this exists: ${untrusted(data.cell.rationale, 500)}`);
2141
+ if ((_b = data.cell) == null ? void 0 : _b.error) lines.push(`Last attempt failed: ${untrusted(data.cell.error, 300)}`);
2142
+ lines.push(data.verdict);
2143
+ if (data.performance) {
2144
+ lines.push(`Shown ${data.performance.shown} times, ${data.performance.conversions} conversions.`);
2145
+ }
2146
+ return {
2147
+ content: [{ type: "text", text: lines.join("\n") }],
2148
+ structuredContent: data
2149
+ };
2150
+ })
2151
+ );
2152
+ server.registerTool(
2153
+ "generate_cell",
2154
+ {
2155
+ title: "Generate a cell version",
2156
+ description: "Queue AI generation of a version for one (persona, slot) cell. The worker writes it asynchronously (usually under a minute), brand-locked, and it goes live as a named option the optimizer explores. Also works to regenerate or retry a failed cell. Requires a paid plan (anonymous demo tokens are read-only).",
2157
+ inputSchema: {
2158
+ projectId: projectIdSchema,
2159
+ slotId: import_zod15.z.string().min(1).max(128).describe("The slot id (a matrix column)"),
2160
+ persona: import_zod15.z.string().min(1).max(64).describe("The persona key (a matrix row)")
2161
+ },
2162
+ outputSchema: {
2163
+ slotId: import_zod15.z.string(),
2164
+ persona: import_zod15.z.string(),
2165
+ status: import_zod15.z.literal("pending").describe("Queued \u2014 check get_cell_matrix for progress")
2166
+ },
2167
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2168
+ },
2169
+ withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
2170
+ const id = encodeURIComponent(projectId);
2171
+ await client.post(`/projects/${id}/cells/generate`, { slotId, persona });
2172
+ return {
2173
+ content: [
2174
+ {
2175
+ type: "text",
2176
+ text: `Writing a version of ${untrusted(slotId, 64)} for ${untrusted(persona, 64)} \u2014 it usually goes live in under a minute. Check get_cell_matrix (status flips pending \u2192 live) or get_cell_detail for the story once it lands.`
2177
+ }
2178
+ ],
2179
+ structuredContent: { slotId, persona, status: "pending" }
2180
+ };
2181
+ })
2182
+ );
2183
+ }
2184
+
2026
2185
  // src/server.ts
2027
- var PKG_VERSION = true ? "0.17.7" : "0.0.0-dev";
2186
+ var PKG_VERSION = true ? "0.18.0" : "0.0.0-dev";
2028
2187
  function createMcpServer(client) {
2029
2188
  const server = new import_mcp.McpServer(
2030
2189
  {
@@ -2059,6 +2218,7 @@ function createMcpServer(client) {
2059
2218
  registerVariantWriteTools(server, client);
2060
2219
  registerIntegrationGuideTools(server);
2061
2220
  registerAgentTrafficTools(server, client);
2221
+ registerCellTools(server, client);
2062
2222
  return server;
2063
2223
  }
2064
2224
 
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ApiClient,
4
4
  createMcpServer
5
- } from "./chunk-5PR2LCNN.js";
5
+ } from "./chunk-PX6RCSB2.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/dist/lib.cjs CHANGED
@@ -1886,6 +1886,15 @@ useLayoutOrder(). Non-React sites: sections: ['#hero', '#pricing', '#faq'] (CSS
1886
1886
  the theme's natural order) in window.sentient \u2014 the snippet reorders those elements within
1887
1887
  their shared parent, and applies nothing if any selector drifts.
1888
1888
 
1889
+ ## No-code install order (snippet)
1890
+
1891
+ Three tags in <head>, in this order: the window.sentient config, the inline pre-paint script,
1892
+ then the deferred loader. The middle tag is optional but recommended \u2014 the loader is deferred,
1893
+ so on a return visit the page can paint its original state before the loader runs; the inline
1894
+ script applies the last served decision from the visitor's own device first, with no network
1895
+ call and no cloak. Get its exact bytes from the dashboard's Install page, or from
1896
+ renderSnippetPrePaintScript() in '@sentientui/snippet/install'.
1897
+
1889
1898
  ## Testing the integration
1890
1899
 
1891
1900
  Use '@sentientui/react/testing': renderWithSentient(ui, { variants, slots, persona }) forces
@@ -2047,8 +2056,158 @@ function registerAgentTrafficTools(server, client) {
2047
2056
  );
2048
2057
  }
2049
2058
 
2059
+ // src/tools/cells.ts
2060
+ var import_zod15 = require("zod");
2061
+ var personaOutput = import_zod15.z.object({
2062
+ key: import_zod15.z.string(),
2063
+ displayName: import_zod15.z.string(),
2064
+ description: import_zod15.z.string().nullable(),
2065
+ share: import_zod15.z.number().describe("Fraction of 30-day decided sessions")
2066
+ });
2067
+ var cellOutput = import_zod15.z.object({
2068
+ slotId: import_zod15.z.string(),
2069
+ persona: import_zod15.z.string(),
2070
+ status: import_zod15.z.string().describe("pending | generating | live | error | rejected"),
2071
+ requestedBy: import_zod15.z.string(),
2072
+ armId: import_zod15.z.string().nullable(),
2073
+ rationale: import_zod15.z.string().nullable(),
2074
+ error: import_zod15.z.string().nullable(),
2075
+ updatedAt: import_zod15.z.string(),
2076
+ performance: import_zod15.z.object({
2077
+ shown: import_zod15.z.number(),
2078
+ conversions: import_zod15.z.number(),
2079
+ deltaPct: import_zod15.z.number().nullable().describe("Signed % vs the original conversion rate"),
2080
+ tier: import_zod15.z.string().describe("early | no_baseline | winning | losing | flat")
2081
+ }).optional().describe("Present on live cells: the at-a-glance readout")
2082
+ });
2083
+ function registerCellTools(server, client) {
2084
+ server.registerTool(
2085
+ "get_cell_matrix",
2086
+ {
2087
+ title: "Persona \xD7 slot cell matrix",
2088
+ description: 'The "Who sees what" matrix: which visitor types (personas) have an AI-generated version live in each personalizable region (slot), with per-persona traffic share and the first-miss auto-fill setting. A cell with no row means that visitor type sees the original.' + UNTRUSTED_FIELDS_NOTE,
2089
+ inputSchema: { projectId: projectIdSchema },
2090
+ outputSchema: {
2091
+ slots: import_zod15.z.array(import_zod15.z.object({ slotId: import_zod15.z.string(), displayName: import_zod15.z.string(), supportsForms: import_zod15.z.boolean() })),
2092
+ personas: import_zod15.z.array(personaOutput),
2093
+ cells: import_zod15.z.array(cellOutput),
2094
+ autoFill: import_zod15.z.boolean(),
2095
+ personaSource: import_zod15.z.string().describe("Active persona-set source: default | declared | discovered"),
2096
+ undecidedShare: import_zod15.z.number()
2097
+ },
2098
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
2099
+ },
2100
+ withApiErrorGuidance(async ({ projectId }) => {
2101
+ const id = encodeURIComponent(projectId);
2102
+ const data = await client.get(`/projects/${id}/cells`);
2103
+ const byCell = new Map(data.cells.map((c) => [`${c.slotId}::${c.persona}`, c]));
2104
+ const lines = [
2105
+ `Slots: ${data.slots.map((s) => untrusted(s.displayName)).join(", ") || "(none published)"}`,
2106
+ `Auto-fill on first miss: ${data.autoFill ? "ON" : "off"} \xB7 persona source: ${data.personaSource}`
2107
+ ];
2108
+ for (const p of data.personas) {
2109
+ const states = data.slots.map((s) => {
2110
+ const c = byCell.get(`${s.slotId}::${p.key}`);
2111
+ return `${untrusted(s.displayName, 40)}: ${c ? c.status : "original"}`;
2112
+ });
2113
+ lines.push(`- ${untrusted(p.displayName)} (${Math.round(p.share * 100)}% of visitors) \u2014 ${states.join(" \xB7 ")}`);
2114
+ }
2115
+ lines.push(`Undecided visitors (${Math.round(data.undecidedShare * 100)}%) always see the original.`);
2116
+ return {
2117
+ content: [{ type: "text", text: lines.join("\n") }],
2118
+ structuredContent: data
2119
+ };
2120
+ })
2121
+ );
2122
+ server.registerTool(
2123
+ "get_cell_detail",
2124
+ {
2125
+ title: "Cell story",
2126
+ description: "One cell's full story: the generated option a visitor type sees in a slot, why it was written (the stored generation rationale), and how it is performing vs the original (deterministic plain-language verdict)." + UNTRUSTED_FIELDS_NOTE,
2127
+ inputSchema: {
2128
+ projectId: projectIdSchema,
2129
+ slotId: import_zod15.z.string().min(1).max(128).describe("The slot id (a matrix column)"),
2130
+ persona: import_zod15.z.string().min(1).max(64).describe("The persona key (a matrix row)")
2131
+ },
2132
+ outputSchema: {
2133
+ cell: cellOutput.nullable(),
2134
+ arm: import_zod15.z.object({
2135
+ id: import_zod15.z.string(),
2136
+ displayName: import_zod15.z.string().nullable(),
2137
+ content: import_zod15.z.string().nullable(),
2138
+ blocks: import_zod15.z.unknown().nullable()
2139
+ }).nullable(),
2140
+ performance: import_zod15.z.object({
2141
+ shown: import_zod15.z.number(),
2142
+ conversions: import_zod15.z.number(),
2143
+ conversionRate: import_zod15.z.number(),
2144
+ baselineShown: import_zod15.z.number(),
2145
+ baselineConversions: import_zod15.z.number(),
2146
+ baselineConversionRate: import_zod15.z.number()
2147
+ }).nullable(),
2148
+ verdict: import_zod15.z.string()
2149
+ },
2150
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
2151
+ },
2152
+ withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
2153
+ var _a, _b;
2154
+ const id = encodeURIComponent(projectId);
2155
+ const data = await client.get(`/projects/${id}/cells/detail?slotId=${encodeURIComponent(slotId)}&persona=${encodeURIComponent(persona)}`);
2156
+ const lines = [];
2157
+ if (data.arm) {
2158
+ lines.push(
2159
+ data.arm.content !== null ? `They see: "${untrusted(data.arm.content, 300)}"` : `They see a designed layout (option ${untrusted(data.arm.id, 64)}).`
2160
+ );
2161
+ } else {
2162
+ lines.push("No generated version is live for this cell.");
2163
+ }
2164
+ if ((_a = data.cell) == null ? void 0 : _a.rationale) lines.push(`Why this exists: ${untrusted(data.cell.rationale, 500)}`);
2165
+ if ((_b = data.cell) == null ? void 0 : _b.error) lines.push(`Last attempt failed: ${untrusted(data.cell.error, 300)}`);
2166
+ lines.push(data.verdict);
2167
+ if (data.performance) {
2168
+ lines.push(`Shown ${data.performance.shown} times, ${data.performance.conversions} conversions.`);
2169
+ }
2170
+ return {
2171
+ content: [{ type: "text", text: lines.join("\n") }],
2172
+ structuredContent: data
2173
+ };
2174
+ })
2175
+ );
2176
+ server.registerTool(
2177
+ "generate_cell",
2178
+ {
2179
+ title: "Generate a cell version",
2180
+ description: "Queue AI generation of a version for one (persona, slot) cell. The worker writes it asynchronously (usually under a minute), brand-locked, and it goes live as a named option the optimizer explores. Also works to regenerate or retry a failed cell. Requires a paid plan (anonymous demo tokens are read-only).",
2181
+ inputSchema: {
2182
+ projectId: projectIdSchema,
2183
+ slotId: import_zod15.z.string().min(1).max(128).describe("The slot id (a matrix column)"),
2184
+ persona: import_zod15.z.string().min(1).max(64).describe("The persona key (a matrix row)")
2185
+ },
2186
+ outputSchema: {
2187
+ slotId: import_zod15.z.string(),
2188
+ persona: import_zod15.z.string(),
2189
+ status: import_zod15.z.literal("pending").describe("Queued \u2014 check get_cell_matrix for progress")
2190
+ },
2191
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
2192
+ },
2193
+ withApiErrorGuidance(async ({ projectId, slotId, persona }) => {
2194
+ const id = encodeURIComponent(projectId);
2195
+ await client.post(`/projects/${id}/cells/generate`, { slotId, persona });
2196
+ return {
2197
+ content: [
2198
+ {
2199
+ type: "text",
2200
+ text: `Writing a version of ${untrusted(slotId, 64)} for ${untrusted(persona, 64)} \u2014 it usually goes live in under a minute. Check get_cell_matrix (status flips pending \u2192 live) or get_cell_detail for the story once it lands.`
2201
+ }
2202
+ ],
2203
+ structuredContent: { slotId, persona, status: "pending" }
2204
+ };
2205
+ })
2206
+ );
2207
+ }
2208
+
2050
2209
  // src/server.ts
2051
- var PKG_VERSION = true ? "0.17.7" : "0.0.0-dev";
2210
+ var PKG_VERSION = true ? "0.18.0" : "0.0.0-dev";
2052
2211
  function createMcpServer(client) {
2053
2212
  const server = new import_mcp.McpServer(
2054
2213
  {
@@ -2083,6 +2242,7 @@ function createMcpServer(client) {
2083
2242
  registerVariantWriteTools(server, client);
2084
2243
  registerIntegrationGuideTools(server);
2085
2244
  registerAgentTrafficTools(server, client);
2245
+ registerCellTools(server, client);
2086
2246
  return server;
2087
2247
  }
2088
2248
  // Annotate the CommonJS export names for ESM import in node:
package/dist/lib.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ApiError,
5
5
  PUBLIC_UI_RESOURCE_URIS,
6
6
  createMcpServer
7
- } from "./chunk-5PR2LCNN.js";
7
+ } from "./chunk-PX6RCSB2.js";
8
8
  export {
9
9
  ApiClient,
10
10
  ApiError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/mcp",
3
- "version": "0.17.7",
3
+ "version": "0.18.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",