@apex-inc/mcp-server 0.27.2 → 0.27.4
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/dist/adoption-compose.d.ts +30 -0
- package/dist/adoption-compose.d.ts.map +1 -0
- package/dist/adoption-compose.js +69 -0
- package/dist/adoption-compose.js.map +1 -0
- package/dist/prompts.js +4 -4
- package/dist/tools.d.ts +99 -73
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +71 -52
- package/dist/tools.js.map +1 -1
- package/guardrails/experiment-rules.md +11 -0
- package/package.json +1 -1
- package/skills/apex-adoption/SKILL.md +9 -6
- package/skills/apex-experimentation/SKILL.md +12 -2
|
@@ -13,6 +13,17 @@
|
|
|
13
13
|
## Implementation
|
|
14
14
|
- Resolve the variant with the canonical Apex resolver (`useApexVariant` on web,
|
|
15
15
|
`Apex.getVariant()` on mobile). Both fire the `experiment_exposure` event.
|
|
16
|
+
- Merchants still write `const variant = useApexVariant("exp_…")` and branch.
|
|
17
|
+
First HTML is flicker-free only when the server already assigned the visitor
|
|
18
|
+
and passed that arm (`initialVariant` or `<ApexProvider assignments>`).
|
|
19
|
+
Mint or read `apex_vid` on the document request and assign with the same
|
|
20
|
+
rules as `GET /api/experiments/{id}/assign`. If you use Next.js,
|
|
21
|
+
`npm install @apex-inc/next @apex-inc/react`, then
|
|
22
|
+
`apexVisitorMiddleware`, `assignExperiments`,
|
|
23
|
+
`<ApexProvider assignments>`. The hook alone is not flicker-free. A
|
|
24
|
+
client-only SPA cannot know the arm before first HTML; do not promise
|
|
25
|
+
no flicker there. Do not hide the page until assign (worse LCP).
|
|
26
|
+
Creating the experiment via MCP does not remove flicker.
|
|
16
27
|
- The variant must fall back to control on ineligible / error.
|
|
17
28
|
- For VISUAL changes, confirm the arms actually differ — screenshot both. Class
|
|
18
29
|
overrides can silently no-op (e.g. when the codebase doesn't tailwind-merge),
|
package/package.json
CHANGED
|
@@ -11,7 +11,9 @@ The Adoption Engine tracks whether each end-user has adopted each feature, nudge
|
|
|
11
11
|
|
|
12
12
|
- A **milestone** = "has this end-user adopted feature X?" — defined by an event they fire (`report_run`) or a trait (`plan = pro`). It is per-user and per-feature. It is **not** a goal (goals are workspace/experiment/journey success).
|
|
13
13
|
- Apex ranks a user's unmet milestones by priority and sends the single top nudge per cadence — so nobody gets spammed.
|
|
14
|
-
- A
|
|
14
|
+
- A deterministic holdout is kept automatically (~5–10%); the report shows lift vs that holdout. Users in an active experiment are never nudged.
|
|
15
|
+
- New milestones default to **future-only** (`enroll_mode: "future"`): only people who sign up after the milestone is created can enter the nudge. Pass `enroll_mode: "all"` to include the historical roster. Existing milestones without `enrollMode` stay "all" so live lists do not shrink.
|
|
16
|
+
- `gap_hours` wins over `gap_days`. `0` hours or days means right away. Do not assume a 1-day floor.
|
|
15
17
|
- A milestone has **two possible journeys**, and they are opposites:
|
|
16
18
|
- **Nudge** (audience-entry): fires when a user *hasn't* adopted within the gap window. Drives them toward the feature.
|
|
17
19
|
- **Celebration** (fires once on adoption): congratulates the user the moment they *do* reach the milestone, and tells them what's next. Scaffold both legs in one call with `compose_adoption_milestone` + `scaffold: "both"` (this mirrors the dashboard sentence on-ramp).
|
|
@@ -79,19 +81,20 @@ raw `predicate`:
|
|
|
79
81
|
|
|
80
82
|
### "Nudge users who haven't run their first report"
|
|
81
83
|
1. `list_adoption_milestones` → check it doesn't already exist.
|
|
82
|
-
2. `create_adoption_milestone` with `name: "Ran first report"`, `
|
|
84
|
+
2. `create_adoption_milestone` with `name: "Ran first report"`, `feature_key: "reporting"`, `adopted_when_kind: "event"`, `event_name: "report_run"`, `gap_days: 3` (or `gap_hours` for a same-day clock). Defaults to future-only. Leave `active` off first. Read the `warnings` in the response — a duplicate event or an event never seen on an identified person is a hard honesty problem.
|
|
83
85
|
3. Tell the user it's created as a draft, and that turning it on (`update_adoption_milestone` with `active: true`) starts nudging through their journeys (opt-out + caps respected).
|
|
84
86
|
4. Later: `get_adoption_metrics` → report the lift-vs-holdout so they know whether it worked.
|
|
85
87
|
|
|
86
88
|
### "Congratulate users when they start their first experiment"
|
|
87
89
|
1. `compose_adoption_milestone` with `name: "Started first experiment"`, `featureKey: "experiments"`, `adoptedWhenKind: "event"`, `eventName: "experiment_started"`, `scaffold: "celebration"`. This creates the milestone AND a draft celebration journey wired to fire once, the moment the user reaches it.
|
|
88
|
-
2. Tell the user the celebration journey is a **draft
|
|
90
|
+
2. Tell the user the celebration journey is a **draft**. Preview with `preview_communication`, then `publish_journey` (dry-run first).
|
|
89
91
|
3. Turning the milestone on is separate from publishing the journey; both must be live for the celebration to send.
|
|
90
92
|
|
|
91
93
|
### "Set up a full milestone — nudge the ones who don't, celebrate the ones who do"
|
|
92
|
-
1. `compose_adoption_milestone` with `scaffold: "both"` (
|
|
93
|
-
2. The response carries `
|
|
94
|
-
3.
|
|
94
|
+
1. `compose_adoption_milestone` with `scaffold: "both"` (default). Example: `name: "Started first experiment"`, `feature_key: "experiments"`, `adopted_when_kind: "event"`, `event_name: "experiment_started"`, `gap_hours: 1` (or `gap_days: 7` in production). One call creates the milestone plus BOTH draft journeys.
|
|
95
|
+
2. The response carries `nudge_journey_id`, `celebration_journey_id`, and `warnings`. Preview the letter with `preview_communication`, then `publish_journey` (dry-run first). Do not activate until the letter is previewed and the journey is published.
|
|
96
|
+
3. Do not scaffold a celebration on an event that already has a published operator letter (experiment started, domain verified, etc.) — pass `scaffold: "nudge"`.
|
|
97
|
+
4. Turn the milestone on (`update_adoption_milestone` `active: true`) once its nudge is published, so it can measure lift vs the holdout.
|
|
95
98
|
|
|
96
99
|
### "Put my milestones in the order accounts should hit them"
|
|
97
100
|
1. `list_adoption_milestones` → get the ids.
|
|
@@ -73,7 +73,13 @@ Interactive flows: MCP prompts `new-experiment` and `experiment-review` orchestr
|
|
|
73
73
|
- **Target URL is this workspace.** `target_url` must be a host named on the **active** workspace (`list_workspace_environments` + the workspace site). If the merchant already named a workspace, switch to it — do not ask which workspace again. If they have multiple hosts on that workspace, ask which URL. Never guess `apex.inc` or any other workspace's domain.
|
|
74
74
|
- **Mobile/Capacitor experiments capture on-device.** When the experiment runs on an authed in-app screen (servers can't reach it), tell the dev to call `Apex.captureVariantScreenshot({ experimentId, variantKey })` on the variant's screen, keyed to the resolved variant in a `useEffect`, in a debug build (`Apex.initialize({ ..., debug: true })`). It no-ops in production and lands the shot on the dashboard card + gallery like web/agent captures.
|
|
75
75
|
- **Exposure auto-fires.** When a variant resolves via `useApexVariant` (web) or `Apex.getVariant()` (mobile), the SDK fires the canonical `experiment_exposure` event — the denominator for results. You don't fire it manually.
|
|
76
|
-
- **
|
|
76
|
+
- **First HTML / flicker.** Merchants still write `const variant = useApexVariant("exp_…")` and branch in React. That hook is not flicker-free by itself. First HTML is flicker-free only when it is already the assigned arm.
|
|
77
|
+
- **SSR (Next, Remix):** on the document request, mint or read the `apex_vid` cookie. Assign with the same rules as `GET /api/experiments/{id}/assign`. Pass that arm: `useApexVariant(id, { initialVariant, search })` or `<ApexProvider assignments={{ [id]: variant }}>`. `{ search }` also makes `_apex_preview` match on first HTML. Without the server arm, the first HTML is control; after `/assign` returns, React swaps. That swap is the flicker.
|
|
78
|
+
- **If you use Next.js:** `npm install @apex-inc/next` (visitor middleware + `assignExperiments`). Pass the result to `<ApexProvider assignments>`.
|
|
79
|
+
- **Client-only SPA:** the hook cannot know the arm before first HTML. Do not promise no flicker.
|
|
80
|
+
- **Snippet mode** (Apex mutates the DOM) is a different path. Do not conflate. Do not hide the page until `/assign` returns as the React-hook promise (worse LCP).
|
|
81
|
+
- **MCP does not serve the visitor page.** `create_experiment` / `activate_experiment` do not remove flicker. The merchant app must still pass the server arm.
|
|
82
|
+
- Pass the server arm into the hook or provider. `@apex-inc/react@0.4.3` accepts `initialVariant` and `ApexProvider assignments`.
|
|
77
83
|
- **Don't launch a dead experiment.** `activate_experiment` is gated on `verify_experiment_wiring`. Code-wired (SDK-hook) web experiments are safe to activate before their code ships: the server holds them in `pending_deployment` ("Waiting on deploy"). The 14-day clock starts at the first **recorded** visit (a tester on a named Beta site, or a live visit) — not merge, not activate. Testers on a named Beta site count in the winner. Localhost/dev is recorded as Dev and does not start the window or decide the winner. Assign with no Origin is Unclassified (no enrollment). Backend assign must send `environment` or `x-apex-web-environment`. Non-SDK experiments with unwired arms are refused (pass `force: true` to override). Run `verify_experiment_wiring({ experimentId })` after the deploy lands to confirm both arms are live.
|
|
78
84
|
|
|
79
85
|
### Journey-arm experiments (any surface)
|
|
@@ -126,6 +132,8 @@ Omitting `intent` when it's required returns 409 `intent_required` naming both.
|
|
|
126
132
|
|
|
127
133
|
`GET /api/experiments/{experimentId}/assign` returns `{ variant, experimentId, visitorId }`. Assignment is **deterministic** from a stable visitor id (cookie `apex_vid` in browsers).
|
|
128
134
|
|
|
135
|
+
The same assign call is how a Next/Remix document request gets flicker-free first HTML: read or mint `apex_vid`, assign, pass the arm into `useApexVariant` / `ApexProvider`. If you use Next.js, `@apex-inc/next` does that. See First HTML / flicker above.
|
|
136
|
+
|
|
129
137
|
For **server-side** sends (e.g. PLG email or API-triggered notifications):
|
|
130
138
|
|
|
131
139
|
1. Use a **stable string per recipient** as the visitor id (e.g. hashed user id). Reuse the same id for every assignment call for that user for that experiment.
|
|
@@ -137,7 +145,7 @@ Then branch notification copy or UI on `variant` (`control` vs `variant_b`).
|
|
|
137
145
|
## Instrumentation checklist
|
|
138
146
|
|
|
139
147
|
1. **Create and activate** the experiment in Apex (UI or MCP).
|
|
140
|
-
2. **Assign variant** before rendering the experience.
|
|
148
|
+
2. **Assign variant** before rendering the experience. On SSR, that means on the document request, then pass the arm into the hook or provider.
|
|
141
149
|
3. **Track primary metric** events with consistent names (e.g. `notification_sent`, `notification_opened`, `goal_completed`) and include `experimentId` and `variant` in properties when possible.
|
|
142
150
|
4. **Log a prediction** before launch when using the full intelligence loop (calibration).
|
|
143
151
|
5. **Decide** with `get_results` / dashboard — avoid peeking without a rule (pre-registered sample size, Bayesian policy, or fixed run duration).
|
|
@@ -152,6 +160,8 @@ Match Apex UI terminology in user-facing copy: e.g. **Adaptive traffic allocatio
|
|
|
152
160
|
- Declaring an SDK / Cursor draft ready from hosted screenshots of a public URL. The variant is local until deploy — attach localhost shots with `attach_experiment_asset`.
|
|
153
161
|
- Starting an experiment without a **clear primary metric** tied to the belief.
|
|
154
162
|
- Using different visitor ids for the same user across channels (breaks consistent assignment).
|
|
163
|
+
- Promising no flicker from `useApexVariant` without passing a server arm. Creating the experiment via MCP does not remove flicker.
|
|
164
|
+
- Hiding the page until `/assign` returns (worse LCP; not the React-hook promise).
|
|
155
165
|
- Changing variant implementation mid-flight without versioning (invalidates analysis).
|
|
156
166
|
- Only using p-values without effect size or business context.
|
|
157
167
|
|