@apex-inc/mcp-server 0.40.0 → 0.41.1

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.
Files changed (74) hide show
  1. package/README.md +1 -1
  2. package/dist/api-client.d.ts +21 -1
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +55 -2
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/auth.js +2 -1
  7. package/dist/auth.js.map +1 -1
  8. package/dist/base-url.d.ts +8 -0
  9. package/dist/base-url.d.ts.map +1 -0
  10. package/dist/base-url.js +10 -0
  11. package/dist/base-url.js.map +1 -0
  12. package/dist/index.js +3 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/prompts.d.ts +14 -0
  15. package/dist/prompts.d.ts.map +1 -1
  16. package/dist/prompts.js +31 -6
  17. package/dist/prompts.js.map +1 -1
  18. package/dist/repo-binding.d.ts +8 -0
  19. package/dist/repo-binding.d.ts.map +1 -0
  20. package/dist/repo-binding.js +13 -0
  21. package/dist/repo-binding.js.map +1 -0
  22. package/dist/resources.d.ts +13 -0
  23. package/dist/resources.d.ts.map +1 -1
  24. package/dist/resources.js +16 -0
  25. package/dist/resources.js.map +1 -1
  26. package/dist/setup-catchup-tools.d.ts +279 -1
  27. package/dist/setup-catchup-tools.d.ts.map +1 -1
  28. package/dist/setup-catchup-tools.js +350 -24
  29. package/dist/setup-catchup-tools.js.map +1 -1
  30. package/dist/setup-checklist.d.ts +68 -0
  31. package/dist/setup-checklist.d.ts.map +1 -0
  32. package/dist/setup-checklist.js +128 -0
  33. package/dist/setup-checklist.js.map +1 -0
  34. package/dist/setup-flow.d.ts +14 -0
  35. package/dist/setup-flow.d.ts.map +1 -0
  36. package/dist/setup-flow.js +52 -0
  37. package/dist/setup-flow.js.map +1 -0
  38. package/dist/tool-groups.d.ts.map +1 -1
  39. package/dist/tool-groups.js +10 -0
  40. package/dist/tool-groups.js.map +1 -1
  41. package/dist/tool-shared.d.ts.map +1 -1
  42. package/dist/tool-shared.js +11 -4
  43. package/dist/tool-shared.js.map +1 -1
  44. package/dist/tools/ad-platforms.d.ts +1 -1
  45. package/dist/tools/ad-platforms.js +23 -23
  46. package/dist/tools/ad-platforms.js.map +1 -1
  47. package/dist/tools/adoption.js +2 -2
  48. package/dist/tools/adoption.js.map +1 -1
  49. package/dist/tools/context.d.ts.map +1 -1
  50. package/dist/tools/context.js +15 -33
  51. package/dist/tools/context.js.map +1 -1
  52. package/dist/tools/experiments.d.ts +2 -2
  53. package/dist/tools/experiments.d.ts.map +1 -1
  54. package/dist/tools/experiments.js +21 -21
  55. package/dist/tools/experiments.js.map +1 -1
  56. package/dist/tools/letters.d.ts +6 -6
  57. package/dist/tools/letters.js +1 -1
  58. package/dist/tools/letters.js.map +1 -1
  59. package/dist/tools/measurement.d.ts +2 -2
  60. package/dist/tools/measurement.d.ts.map +1 -1
  61. package/dist/tools/measurement.js +4 -3
  62. package/dist/tools/measurement.js.map +1 -1
  63. package/dist/tools.d.ts +307 -11
  64. package/dist/tools.d.ts.map +1 -1
  65. package/package.json +3 -2
  66. package/skills/apex-adoption/SKILL.md +1 -1
  67. package/skills/apex-communications/SKILL.md +2 -2
  68. package/skills/apex-experimentation/SKILL.md +6 -6
  69. package/skills/apex-growth-intelligence/SKILL.md +6 -6
  70. package/skills/apex-growth-tracking/SKILL.md +4 -4
  71. package/skills/apex-integration-cookbook/SKILL.md +11 -15
  72. package/skills/apex-journeys/SKILL.md +3 -3
  73. package/skills/apex-partner-network/SKILL.md +1 -1
  74. package/skills/apex-setup/SKILL.md +69 -0
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import { apiGet, apiPost, apiPatch, apiDelete, getActiveWorkspace, } from "../api-client.js";
5
5
  import { resolveExperimentHypothesis } from "../experiment-copy.js";
6
6
  import { activationLiveLine, shouldRefuseActivationWiring } from "../activation-wiring.js";
7
+ import { apexBaseUrl } from "../base-url.js";
7
8
  import { fetchLiveTestHits, formatLiveTestStop, isFileStop, isObjectStop, } from "../live-test-guard.js";
8
9
  import { hasConflictAdvisory, renderConflictAdvisory, } from "../conflict-advisory.js";
9
10
  import { APEX, controlPctOf, daysRunningOf, findVariant, formatArmLine, titleCaseEvent, assertTargetUrlForActiveWorkspace, errMsg, resolveCreateSurface, } from "../tool-shared.js";
@@ -35,7 +36,7 @@ async function createAdHoldout(args) {
35
36
  if (adSetIds.length > 20)
36
37
  return textError("An Ad holdout covers at most 20 ad sets.");
37
38
  // What the workspace has already set up for this network: the connected
38
- // ad account, whether the shop agreed to hashed emails going to the
39
+ // ad account, whether the owner agreed to hashed emails going to the
39
40
  // network, and (Meta) whether Apex proved it can hold people back there.
40
41
  let prereqs = null;
41
42
  try {
@@ -57,7 +58,7 @@ async function createAdHoldout(args) {
57
58
  }
58
59
  else {
59
60
  if (!net.audienceAcked) {
60
- blockers.push(`The shop has not yet agreed to hashed emails going to ${networkWord}. Finish the "Let Apex hold people back from your ads" setup step.`);
61
+ blockers.push(`The owner has not yet agreed to hashed emails going to ${networkWord}. Finish the "Let Apex hold people back from your ads" setup step.`);
61
62
  }
62
63
  if (net.attachAvailable && !net.accessProven) {
63
64
  blockers.push(`Apex has not yet proved its ${networkWord} token can put a do-not-show list on this account. The same setup step runs the two checks.`);
@@ -98,7 +99,7 @@ async function createAdHoldout(args) {
98
99
  what_is_measured: "App installs in both lanes (saw the ads vs held back). Purchases can be a secondary metric.",
99
100
  hypothesis,
100
101
  honesty: [
101
- `Nothing counts until Apex confirms the do-not-show list is on every ad set${attachMode === "manual" ? ` — with manual attach the shop adds the audience as an exclusion in ${networkWord} Ads` : ""}.`,
102
+ `Nothing counts until Apex confirms the do-not-show list is on every ad set${attachMode === "manual" ? ` — with manual attach the owner adds the audience as an exclusion in ${networkWord} Ads` : ""}.`,
102
103
  "Results freeze if the list comes off mid-run; start a new holdout to measure again.",
103
104
  "Installs are counted for known people only (someone with an email or phone Apex can hash).",
104
105
  ],
@@ -149,7 +150,7 @@ async function createAdHoldout(args) {
149
150
  experimentId: exp.id,
150
151
  status: exp.status,
151
152
  _next: [
152
- `activate_experiment({ experimentId: "${exp.id}" }) — Apex draws the held-back list and ${attachMode === "apex" ? `puts it on the ${networkWord} ad sets` : `makes the audience for the shop to add as an exclusion in ${networkWord} Ads`}.`,
153
+ `activate_experiment({ experimentId: "${exp.id}" }) — Apex draws the held-back list and ${attachMode === "apex" ? `puts it on the ${networkWord} ad sets` : `makes the audience for the owner to add as an exclusion in ${networkWord} Ads`}.`,
153
154
  `verify_ad_exclusion({ experiment_id: "${exp.id}" }) — confirm the list is on every ad set. Counting starts after the washout.`,
154
155
  `get_results({ experimentId: "${exp.id}" }) — the two lanes, once counting.`,
155
156
  ],
@@ -323,14 +324,14 @@ export const experimentTools = {
323
324
  guardrailType: z.enum(["rate", "revenue", "duration"]).optional().describe("How the guardrail is measured. 'rate' (default) = how often the harm event fires. 'revenue' = sum a value (e.g. refund $)."),
324
325
  guardrailThreshold: z.number().optional().describe("Harm margin for the guardrail, RELATIVE (0.25 = flag a >25% rise). Defaults to 0.25."),
325
326
  predictionMagnitude: z.string().optional().describe("Your committed prediction of the effect, e.g. '10% lift'. Recorded with 'agent' provenance and used for calibration. Required before the experiment can be ACTIVATED."),
326
- surface: z.enum(["web", "mobile", "paid_channel"]).optional().describe("The PROPERTY the experiment runs in (not the SDK used): 'web' for a website, 'mobile' for a native / Capacitor / React-Native app, 'paid_channel' for an Ad holdout (Apex holds a share of known people back from the shop's ads on one network and measures the installs the ads actually caused — pass paid_channel too). OPTIONAL for web/mobile — when omitted it is auto-detected from repo signals (capacitor.config.* / @apex-inc/capacitor-plugin → mobile) and the workspace's registered data sources; pass it explicitly only to override or when both web and mobile sources exist. Sets the dashboard data-source label (Website vs Mobile app vs Ads)."),
327
+ surface: z.enum(["web", "mobile", "paid_channel"]).optional().describe("The PROPERTY the experiment runs in (not the SDK used): 'web' for a website, 'mobile' for a native / Capacitor / React-Native app, 'paid_channel' for an Ad holdout (Apex holds a share of known people back from the workspace's ads on one network and measures the installs the ads actually caused — pass paid_channel too). OPTIONAL for web/mobile — when omitted it is auto-detected from repo signals (capacitor.config.* / @apex-inc/capacitor-plugin → mobile) and the workspace's registered data sources; pass it explicitly only to override or when both web and mobile sources exist. Sets the dashboard data-source label (Website vs Mobile app vs Ads)."),
327
328
  paid_channel: z
328
329
  .object({
329
330
  network: z.enum(["meta", "google", "tiktok", "linkedin"]).describe("Ad network the ad sets run on. Apex attaches the do-not-show list itself on Meta only; other networks are manual attach."),
330
331
  ad_account_id: z.string().optional().describe("The connected ad account the ad sets belong to. Omit to use the account the workspace connected for this network."),
331
332
  ad_set_ids: z.array(z.string()).min(1).describe("Ad set ids to hold people back from (max 20). Frozen at create; changing them is a new holdout."),
332
333
  holdout_percent: z.number().min(0.05).max(0.5).describe("Share of the known population held back, 0.05–0.5 (0.2 = one in five)."),
333
- attach_mode: z.enum(["apex", "manual"]).optional().describe("'apex' = Apex puts the list on the ad sets and checks daily (Meta only). 'manual' = the shop adds the audience as an exclusion by hand; Apex checks daily and counts nothing until it is on. Defaults to 'apex' on Meta, 'manual' elsewhere."),
334
+ attach_mode: z.enum(["apex", "manual"]).optional().describe("'apex' = Apex puts the list on the ad sets and checks daily (Meta only). 'manual' = the owner adds the audience as an exclusion by hand; Apex checks daily and counts nothing until it is on. Defaults to 'apex' on Meta, 'manual' elsewhere."),
334
335
  washout_days: z.number().int().min(0).max(30).optional().describe("Days after the list lands before installs count, so pre-list ad exposure washes out. Default 3."),
335
336
  population_segment_id: z.string().optional().describe("Optional Segment id narrowing who can be drawn."),
336
337
  })
@@ -1212,8 +1213,7 @@ export const experimentTools = {
1212
1213
  }
1213
1214
  let isReachable = false;
1214
1215
  try {
1215
- const BASE_URL = process.env.APEX_URL || "http://localhost:3001";
1216
- const res = await fetch(`${BASE_URL}/api/experiments/${experimentId}/assign`, {
1216
+ const res = await fetch(`${apexBaseUrl()}/api/experiments/${experimentId}/assign`, {
1217
1217
  signal: AbortSignal.timeout(5000),
1218
1218
  });
1219
1219
  isReachable = res.ok;
@@ -1485,7 +1485,7 @@ export const experimentTools = {
1485
1485
  blocker: d.blocker ?? null,
1486
1486
  existing_belief_id: d.existingBeliefId ?? null,
1487
1487
  in_plain_words: d.suggestions.length > 0
1488
- ? `${d.suggestions.length} verdict${d.suggestions.length === 1 ? "" : "s"} worth writing down. Show the shop the sentence; they can edit it before accept_belief_suggestion writes it as one study.`
1488
+ ? `${d.suggestions.length} verdict${d.suggestions.length === 1 ? "" : "s"} worth writing down. Show the owner the sentence; they can edit it before accept_belief_suggestion writes it as one study.`
1489
1489
  : d.blocker
1490
1490
  ? blockerWords[d.blocker] ?? `Not offered: ${d.blocker}.`
1491
1491
  : "Nothing worth writing down right now. Suggestions appear when an experiment settles cleanly.",
@@ -1499,10 +1499,10 @@ export const experimentTools = {
1499
1499
  },
1500
1500
  },
1501
1501
  accept_belief_suggestion: {
1502
- description: `${APEX} — Write a settled experiment's verdict down as one belief. Creates the Belief with the experiment as its supporting evidence and generatedFromExperimentId set; its evidence state starts at 'one study' and moves to 'validated' when a second test agrees. 'statement' is the shop's edit of the sentence (the numbers and evidence line stay Apex's). The gate re-runs here, so an experiment list_belief_suggestions would not offer is refused with its blocker. Writing it twice returns the existing belief. Admin only.`,
1502
+ description: `${APEX} — Write a settled experiment's verdict down as one belief. Creates the Belief with the experiment as its supporting evidence and generatedFromExperimentId set; its evidence state starts at 'one study' and moves to 'validated' when a second test agrees. 'statement' is the workspace's edit of the sentence (the numbers and evidence line stay Apex's). The gate re-runs here, so an experiment list_belief_suggestions would not offer is refused with its blocker. Writing it twice returns the existing belief. Admin only.`,
1503
1503
  schema: z.object({
1504
1504
  experimentId: z.string().describe("The settled experiment to write down."),
1505
- statement: z.string().min(8).max(600).optional().describe("The shop's wording of the belief. Omit to accept Apex's sentence as written."),
1505
+ statement: z.string().min(8).max(600).optional().describe("The owner's wording of the belief. Omit to accept Apex's sentence as written."),
1506
1506
  }),
1507
1507
  handler: async (args) => {
1508
1508
  const id = args.experimentId.trim();
@@ -1521,7 +1521,7 @@ export const experimentTools = {
1521
1521
  confidence: b.confidence,
1522
1522
  evidence_state: "one_study",
1523
1523
  generated_from_experiment_id: id,
1524
- in_plain_words: "Written down. It is on the shop's Belief Map as one study with this experiment as the evidence. A second test that agrees moves it to validated.",
1524
+ in_plain_words: "Written down. It is on the workspace's Belief Map as one study with this experiment as the evidence. A second test that agrees moves it to validated.",
1525
1525
  }, null, 2),
1526
1526
  }],
1527
1527
  };
@@ -1539,7 +1539,7 @@ export const experimentTools = {
1539
1539
  },
1540
1540
  },
1541
1541
  verify_ad_exclusion: {
1542
- description: `${APEX} — Ad holdout only (surface paid_channel). Ask Apex to check, right now, whether the do-not-show list is on every ad set the holdout covers, and record the answer. Nothing is counted until this reads "attached"; the daily check runs on its own, so call this after the list lands or after the shop attached it by hand. Rate-limited: a recent check returns the last reading instead of re-reading.`,
1542
+ description: `${APEX} — Ad holdout only (surface paid_channel). Ask Apex to check, right now, whether the do-not-show list is on every ad set the holdout covers, and record the answer. Nothing is counted until this reads "attached"; the daily check runs on its own, so call this after the list lands or after the workspace attached it by hand. Rate-limited: a recent check returns the last reading instead of re-reading.`,
1543
1543
  schema: z.object({
1544
1544
  experimentId: z.string().describe("The Ad holdout experiment id."),
1545
1545
  }),
@@ -1597,11 +1597,11 @@ export const experimentTools = {
1597
1597
  },
1598
1598
  },
1599
1599
  attach_ad_exclusion: {
1600
- description: `${APEX} — Ad holdout only (surface paid_channel, Meta only). Apex puts the held-back list on the holdout's ad sets itself: exactly one exclusion added per ad set, every other targeting key (locations, age, placements, ...) left as read, then read back to confirm. Two steps, always in this order: (1) call with preview=true (default) and SHOW the shop the before/after per ad set — which ad sets get the exclusion, which already have it, which are on another account and will not be touched; (2) once the shop confirms, call with preview=false. The first real attach in a workspace needs acknowledge=true, which records the shop's standing allowance for Apex to edit ad-set targeting (say so before passing it; never pass it without the shop having seen the preview). Refuses with needs_targeting_ack when it is missing, list_not_on_network while the list is still syncing, and not for Google/TikTok/LinkedIn (there the shop adds the list in the network's tools and verify_ad_exclusion checks). Ending, pausing or archiving the holdout takes the list off again on its own. Not billed.`,
1600
+ description: `${APEX} — Ad holdout only (surface paid_channel, Meta only). Apex puts the held-back list on the holdout's ad sets itself: exactly one exclusion added per ad set, every other targeting key (locations, age, placements, ...) left as read, then read back to confirm. Two steps, always in this order: (1) call with preview=true (default) and SHOW the workspace the before/after per ad set — which ad sets get the exclusion, which already have it, which are on another account and will not be touched; (2) once the workspace confirms, call with preview=false. The first real attach in a workspace needs acknowledge=true, which records the workspace's standing allowance for Apex to edit ad-set targeting (say so before passing it; never pass it without the workspace having seen the preview). Refuses with needs_targeting_ack when it is missing, list_not_on_network while the list is still syncing, and not for Google/TikTok/LinkedIn (there the owner adds the list in the network's tools and verify_ad_exclusion checks). Ending, pausing or archiving the holdout takes the list off again on its own. Not billed.`,
1601
1601
  schema: z.object({
1602
1602
  experimentId: z.string().describe("The Ad holdout experiment id."),
1603
1603
  preview: z.boolean().optional().describe("true (default): read-only — return the before/after per ad set and whether the allowance is on file. false: make the edit."),
1604
- acknowledge: z.boolean().optional().describe("Pass true on the first real attach in this workspace, after the shop has seen the preview and agreed that Apex may edit ad-set targeting. Recorded once per network."),
1604
+ acknowledge: z.boolean().optional().describe("Pass true on the first real attach in this workspace, after the owner has seen the preview and agreed that Apex may edit ad-set targeting. Recorded once per network."),
1605
1605
  }),
1606
1606
  handler: async (args) => {
1607
1607
  const id = args.experimentId.trim();
@@ -1642,8 +1642,8 @@ export const experimentTools = {
1642
1642
  ad_sets: rows,
1643
1643
  _next: d.ready
1644
1644
  ? d.ack.present
1645
- ? `Show the shop this table. On its word, call attach_ad_exclusion({ experiment_id: "${id}", preview: false }).`
1646
- : `Show the shop this table and say: confirming allows Apex to add the held-back list as one exclusion on holdout ad sets and remove it when a holdout ends; nothing else on the ad sets is changed. On its word, call attach_ad_exclusion({ experiment_id: "${id}", preview: false, acknowledge: true }).`
1645
+ ? `Show the owner this table. On their word, call attach_ad_exclusion({ experiment_id: "${id}", preview: false }).`
1646
+ : `Show the owner this table and say: confirming allows Apex to add the held-back list as one exclusion on holdout ad sets and remove it when a holdout ends; nothing else on the ad sets is changed. On their word, call attach_ad_exclusion({ experiment_id: "${id}", preview: false, acknowledge: true }).`
1647
1647
  : "Fix the reason above, then preview again.",
1648
1648
  }, null, 2),
1649
1649
  }],
@@ -1686,17 +1686,17 @@ export const experimentTools = {
1686
1686
  catch (err) {
1687
1687
  const message = errMsg(err);
1688
1688
  if (/needs_targeting_ack|\(409\)/.test(message)) {
1689
- return textError(`Apex will not edit ad sets until the shop has allowed it. Call attach_ad_exclusion with preview=true, show the before/after, and on the shop's word call again with preview=false and acknowledge=true.`);
1689
+ return textError(`Apex will not edit ad sets until the owner has allowed it. Call attach_ad_exclusion with preview=true, show the before/after, and on the owner's word call again with preview=false and acknowledge=true.`);
1690
1690
  }
1691
1691
  if (/network_not_supported/.test(message)) {
1692
- return textError("Apex attaches the list itself on Meta only. On other networks the shop adds the list in the network's own tools; verify_ad_exclusion checks it is on.");
1692
+ return textError("Apex attaches the list itself on Meta only. On other networks the owner adds the list in the network's own tools; verify_ad_exclusion checks it is on.");
1693
1693
  }
1694
1694
  return textError(`Could not attach the held-back list: ${message}`);
1695
1695
  }
1696
1696
  },
1697
1697
  },
1698
1698
  detach_ad_exclusion: {
1699
- description: `${APEX} — Ad holdout only (surface paid_channel, Meta only). Take Apex's exclusion back off the holdout's ad sets and delete the held-back list from Meta. Removes only what Apex added: a list the shop attached by hand is refused (not_apex_attached) and the shop removes it in Ads Manager. Ending, pausing or archiving the holdout does this on its own; call this only to undo an attach early. Admin only. Confirm with the shop first: the held-back people start seeing the ads again and the holdout stops measuring.`,
1699
+ description: `${APEX} — Ad holdout only (surface paid_channel, Meta only). Take Apex's exclusion back off the holdout's ad sets and delete the held-back list from Meta. Removes only what Apex added: a list the owner attached by hand is refused (not_apex_attached) and the owner removes it in Ads Manager. Ending, pausing or archiving the holdout does this on its own; call this only to undo an attach early. Admin only. Confirm with the workspace first: the held-back people start seeing the ads again and the holdout stops measuring.`,
1700
1700
  schema: z.object({
1701
1701
  experimentId: z.string().describe("The Ad holdout experiment id."),
1702
1702
  }),
@@ -1728,7 +1728,7 @@ export const experimentTools = {
1728
1728
  catch (err) {
1729
1729
  const message = errMsg(err);
1730
1730
  if (/not_apex_attached|\(409\)/.test(message)) {
1731
- return textError("Apex did not put this list on the ad sets, so it will not take it off. The shop removes the exclusion in Ads Manager.");
1731
+ return textError("Apex did not put this list on the ad sets, so it will not take it off. The owner removes the exclusion in Ads Manager.");
1732
1732
  }
1733
1733
  return textError(`Could not take the held-back list off: ${message}`);
1734
1734
  }