@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 +3 -0
- package/README.md +3 -0
- package/dist/{chunk-5PR2LCNN.js → chunk-PX6RCSB2.js} +161 -1
- package/dist/index.cjs +161 -1
- package/dist/index.js +1 -1
- package/dist/lib.cjs +161 -1
- package/dist/lib.js +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
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
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.
|
|
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