@apex-inc/mcp-server 0.19.0 → 0.21.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apex-inc/mcp-server",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "MCP server for Apex — currently in private beta.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,6 +12,10 @@ The Adoption Engine tracks whether each end-user has adopted each feature, nudge
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
14
  - A ~10% deterministic holdout is kept automatically; the report shows lift vs that holdout, not last-touch attribution. Users in an active experiment are never nudged.
15
+ - A milestone has **two possible journeys**, and they are opposites:
16
+ - **Nudge** (audience-entry): fires when a user *hasn't* adopted within the gap window. Drives them toward the feature.
17
+ - **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).
18
+ - Milestones can be given a sequence **order** (which one accounts reach first). Ordering the full set unlocks the account-view "Path" funnel (a step-by-step drop-off chart). Unordered milestones are treated as independent (order-agnostic coverage).
15
19
 
16
20
  ## When to Activate
17
21
 
@@ -26,7 +30,9 @@ The Adoption Engine tracks whether each end-user has adopted each feature, nudge
26
30
  | `list_adoption_milestones` | See the workspace's current milestones |
27
31
  | `get_adoption_milestone` | Inspect one milestone |
28
32
  | `create_adoption_milestone` | Define a new "adopted feature" milestone |
29
- | `update_adoption_milestone` | Change priority / gap window / turn on/off |
33
+ | `compose_adoption_milestone` | Create a milestone AND scaffold its journey legs (`scaffold: "both" \| "celebration" \| "nudge" \| "none"`) in one call |
34
+ | `update_adoption_milestone` | Change priority / order / gap window / turn on/off |
35
+ | `reorder_adoption_milestones` | Set the sequence order of the whole set (unlocks the Path funnel) |
30
36
  | `delete_adoption_milestone` | Remove a milestone |
31
37
  | `get_adoption_metrics` | Per-milestone funnel + lift-vs-holdout + workspace health |
32
38
  | `list_adoption_features` / `create_adoption_feature` | Tenant-owned feature groupings (group your own events) |
@@ -75,6 +81,21 @@ raw `predicate`:
75
81
  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).
76
82
  4. Later: `get_adoption_metrics` → report the lift-vs-holdout so they know whether it worked.
77
83
 
84
+ ### "Congratulate users when they start their first experiment"
85
+ 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.
86
+ 2. Tell the user the celebration journey is a **draft** — they edit the congratulatory message in the canvas (Communications composer), then publish it.
87
+ 3. Turning the milestone on is separate from publishing the journey; both must be live for the celebration to send.
88
+
89
+ ### "Set up a full milestone — nudge the ones who don't, celebrate the ones who do"
90
+ 1. `compose_adoption_milestone` with `scaffold: "both"` (e.g. `name: "Started first experiment"`, `featureKey: "experiments"`, `adoptedWhenKind: "event"`, `eventName: "experiment_started"`, `gapDays: 7`). One call creates the milestone plus BOTH draft journeys: the nudge (for users who haven't started an experiment within 7 days) and the celebration (for the moment they do). This is exactly what the dashboard sentence on-ramp produces.
91
+ 2. The response carries `nudgeJourneyId` and `celebrationJourneyId` — both are **drafts**. Edit copy in the canvas and publish each.
92
+ 3. Turn the milestone on (`update_adoption_milestone` `active: true`) once its nudge is published, so it can measure lift vs the holdout.
93
+
94
+ ### "Put my milestones in the order accounts should hit them"
95
+ 1. `list_adoption_milestones` → get the ids.
96
+ 2. `reorder_adoption_milestones` with `orderedIds` in the intended sequence (activation before depth).
97
+ 3. Tell the user the account view's "Path" funnel is now unlocked — it shows drop-off between consecutive milestones. Without a full order it stays on the order-agnostic "Coverage" view.
98
+
78
99
  ## Guardrails
79
100
 
80
101
  - Milestones start OFF. Only set `active: true` when the user confirms they want to start sending.
@@ -39,6 +39,33 @@ When a user was anonymous and then becomes known (signup, login, checkout email)
39
39
  - Call **`identify`** with a stable `userId` and pass **`traits.email`** when applicable.
40
40
  - **Server-side** `identify` (webhooks, API routes): pass **`traits.visitorId`** = value of the browser **`apex_vid`** cookie so the stitch links to the real marketing session. Without it, Apex may create a synthetic visitor disconnected from prior events.
41
41
 
42
+ ## B2B accounts + groups (model the company, not just the seat)
43
+
44
+ For B2B, revenue belongs to the **account** (company), not the individual. Add a nested `account` object to the SAME `identify` you already call at login/signup:
45
+
46
+ ```ts
47
+ identify(user.id, {
48
+ email: user.email,
49
+ account: { id: "acct_acme", name: "Acme Inc", plan: "enterprise", seat_count: 25 },
50
+ });
51
+ ```
52
+
53
+ Apex upserts the Account, links the user as a member, and rolls every member's revenue into one account LTV (no double counting). `account.id` is required — Apex never infers the company from a shared email domain.
54
+
55
+ If your product has an intermediate org-unit layer (workspaces / teams / projects), assert it with `groups[]` on the same identify — the "model a team/workspace layer" recipe:
56
+
57
+ ```ts
58
+ identify(user.id, {
59
+ account: { id: "acct_acme", name: "Acme Inc" },
60
+ groups: [
61
+ { id: "ws_prod", name: "Production", kind: "workspace", role: "admin" },
62
+ { id: "team_growth", name: "Growth Team", kind: "team", parent_id: "acct_acme" },
63
+ ],
64
+ });
65
+ ```
66
+
67
+ Each group becomes a sub-account of `parent_id` (defaults to `account.id`) and the user is linked to it. Read it back with `apex.accounts.list()` / `.subaccounts(id)` / `.people(id)` / `.contactGroups(contactId)` (SDK) or `list_accounts` / `list_sub_accounts` / `get_contact_groups` (MCP). Revenue lands on the billed account; a parent's LTV is its own counters, not the sum of children.
68
+
42
69
  ## What to measure by loop phase
43
70
 
44
71
  - **Acquisition**: landing views, campaign params (UTM already captured by snippet where configured), signup started.
@@ -105,6 +105,16 @@ Scan-first additions per vertical — wire where the truth lives:
105
105
  (monthly|annual|quarterly|weekly) — quarantined for MRR otherwise.
106
106
  `seat_count` unlocks seat growth. Account traits: `account_id`,
107
107
  `plan`, `mrr`, `arr`, `seat_count` (typed via ApexAccountTraits).
108
+ For account-level rollup (B2B): send a nested `account` object on
109
+ `identify` — `identify(user.id, { account: { id, name, plan,
110
+ seat_count } })` — so revenue rolls up to the company, not the seat.
111
+ For an intermediate org-unit layer (workspaces / teams / projects),
112
+ add `groups[]` on the SAME identify: `groups: [{ id, name, kind,
113
+ parent_id?, role? }]`. Each group becomes a sub-account of `parent_id`
114
+ (defaults to `account.id`) and the user is linked to it. snake_case
115
+ sub-keys; a group only attaches under an account the user is already
116
+ anchored to. Read back via MCP `list_accounts` / `list_sub_accounts`
117
+ / `get_contact_groups`.
108
118
  - **E-commerce**: returns pipeline → `return_requested` / `return_completed`
109
119
  (goods back) alongside `purchase_refunded` (money back — now carries
110
120
  optional `product_id` for per-SKU attribution). Never conflate the two.