@sentientui/mcp 0.17.6 → 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
  >
@@ -44,10 +44,9 @@ var ApiClient = class {
44
44
  var _a, _b;
45
45
  const res = await fetch(`${this.baseUrl}/v1/mgmt${path}`, {
46
46
  method,
47
- headers: {
48
- authorization: `Bearer ${this.apiKey}`,
49
- "content-type": "application/json"
50
- },
47
+ headers: __spreadValues({
48
+ authorization: `Bearer ${this.apiKey}`
49
+ }, body !== void 0 ? { "content-type": "application/json" } : {}),
51
50
  body: body !== void 0 ? JSON.stringify(body) : void 0
52
51
  });
53
52
  if (res.ok) return res.json();
@@ -1857,6 +1856,15 @@ useLayoutOrder(). Non-React sites: sections: ['#hero', '#pricing', '#faq'] (CSS
1857
1856
  the theme's natural order) in window.sentient \u2014 the snippet reorders those elements within
1858
1857
  their shared parent, and applies nothing if any selector drifts.
1859
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
+
1860
1868
  ## Testing the integration
1861
1869
 
1862
1870
  Use '@sentientui/react/testing': renderWithSentient(ui, { variants, slots, persona }) forces
@@ -2018,8 +2026,158 @@ function registerAgentTrafficTools(server, client) {
2018
2026
  );
2019
2027
  }
2020
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
+
2021
2179
  // src/server.ts
2022
- var PKG_VERSION = true ? "0.17.6" : "0.0.0-dev";
2180
+ var PKG_VERSION = true ? "0.18.0" : "0.0.0-dev";
2023
2181
  function createMcpServer(client) {
2024
2182
  const server = new McpServer(
2025
2183
  {
@@ -2054,6 +2212,7 @@ function createMcpServer(client) {
2054
2212
  registerVariantWriteTools(server, client);
2055
2213
  registerIntegrationGuideTools(server);
2056
2214
  registerAgentTrafficTools(server, client);
2215
+ registerCellTools(server, client);
2057
2216
  return server;
2058
2217
  }
2059
2218
 
package/dist/index.cjs CHANGED
@@ -48,10 +48,9 @@ var ApiClient = class {
48
48
  var _a2, _b;
49
49
  const res = await fetch(`${this.baseUrl}/v1/mgmt${path}`, {
50
50
  method,
51
- headers: {
52
- authorization: `Bearer ${this.apiKey}`,
53
- "content-type": "application/json"
54
- },
51
+ headers: __spreadValues({
52
+ authorization: `Bearer ${this.apiKey}`
53
+ }, body !== void 0 ? { "content-type": "application/json" } : {}),
55
54
  body: body !== void 0 ? JSON.stringify(body) : void 0
56
55
  });
57
56
  if (res.ok) return res.json();
@@ -1863,6 +1862,15 @@ useLayoutOrder(). Non-React sites: sections: ['#hero', '#pricing', '#faq'] (CSS
1863
1862
  the theme's natural order) in window.sentient \u2014 the snippet reorders those elements within
1864
1863
  their shared parent, and applies nothing if any selector drifts.
1865
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
+
1866
1874
  ## Testing the integration
1867
1875
 
1868
1876
  Use '@sentientui/react/testing': renderWithSentient(ui, { variants, slots, persona }) forces
@@ -2024,8 +2032,158 @@ function registerAgentTrafficTools(server, client) {
2024
2032
  );
2025
2033
  }
2026
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
+
2027
2185
  // src/server.ts
2028
- var PKG_VERSION = true ? "0.17.6" : "0.0.0-dev";
2186
+ var PKG_VERSION = true ? "0.18.0" : "0.0.0-dev";
2029
2187
  function createMcpServer(client) {
2030
2188
  const server = new import_mcp.McpServer(
2031
2189
  {
@@ -2060,6 +2218,7 @@ function createMcpServer(client) {
2060
2218
  registerVariantWriteTools(server, client);
2061
2219
  registerIntegrationGuideTools(server);
2062
2220
  registerAgentTrafficTools(server, client);
2221
+ registerCellTools(server, client);
2063
2222
  return server;
2064
2223
  }
2065
2224
 
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ApiClient,
4
4
  createMcpServer
5
- } from "./chunk-FBRHD7R7.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
@@ -79,10 +79,9 @@ var ApiClient = class {
79
79
  var _a, _b;
80
80
  const res = await fetch(`${this.baseUrl}/v1/mgmt${path}`, {
81
81
  method,
82
- headers: {
83
- authorization: `Bearer ${this.apiKey}`,
84
- "content-type": "application/json"
85
- },
82
+ headers: __spreadValues({
83
+ authorization: `Bearer ${this.apiKey}`
84
+ }, body !== void 0 ? { "content-type": "application/json" } : {}),
86
85
  body: body !== void 0 ? JSON.stringify(body) : void 0
87
86
  });
88
87
  if (res.ok) return res.json();
@@ -1887,6 +1886,15 @@ useLayoutOrder(). Non-React sites: sections: ['#hero', '#pricing', '#faq'] (CSS
1887
1886
  the theme's natural order) in window.sentient \u2014 the snippet reorders those elements within
1888
1887
  their shared parent, and applies nothing if any selector drifts.
1889
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
+
1890
1898
  ## Testing the integration
1891
1899
 
1892
1900
  Use '@sentientui/react/testing': renderWithSentient(ui, { variants, slots, persona }) forces
@@ -2048,8 +2056,158 @@ function registerAgentTrafficTools(server, client) {
2048
2056
  );
2049
2057
  }
2050
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
+
2051
2209
  // src/server.ts
2052
- var PKG_VERSION = true ? "0.17.6" : "0.0.0-dev";
2210
+ var PKG_VERSION = true ? "0.18.0" : "0.0.0-dev";
2053
2211
  function createMcpServer(client) {
2054
2212
  const server = new import_mcp.McpServer(
2055
2213
  {
@@ -2084,6 +2242,7 @@ function createMcpServer(client) {
2084
2242
  registerVariantWriteTools(server, client);
2085
2243
  registerIntegrationGuideTools(server);
2086
2244
  registerAgentTrafficTools(server, client);
2245
+ registerCellTools(server, client);
2087
2246
  return server;
2088
2247
  }
2089
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-FBRHD7R7.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.6",
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",