@apex-inc/mcp-server 0.7.0 → 0.9.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/README.md +2 -2
- package/dist/api-client.d.ts +5 -5
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +18 -18
- package/dist/api-client.js.map +1 -1
- package/dist/resources.js +1 -1
- package/dist/resources.js.map +1 -1
- package/dist/tools.d.ts +84 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +198 -57
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/skills/apex-experimentation/SKILL.md +2 -2
- package/skills/apex-growth-intelligence/SKILL.md +2 -2
- package/skills/apex-growth-tracking/SKILL.md +4 -4
- package/skills/apex-integration-cookbook/SKILL.md +7 -7
- package/skills/apex-partner-network/SKILL.md +4 -4
- package/skills/apex-spec/SKILL.md +77 -0
package/dist/tools.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { apiGet, apiPost, apiPatch, apiDelete, postWithIdempotency,
|
|
2
|
+
import { apiGet, apiPost, apiPatch, apiDelete, postWithIdempotency, setActiveWorkspace, getActiveWorkspace, setActiveOrg, getActiveOrg, getUserContext } from "./api-client.js";
|
|
3
3
|
const APEX = "∧ Apex";
|
|
4
|
+
/**
|
|
5
|
+
* MOBX-006 — stable synthetic visitor id for agent-fired events.
|
|
6
|
+
*
|
|
7
|
+
* `/api/events` only counts an event toward wiring detection when it
|
|
8
|
+
* carries a top-level `visitorId`. A per-process random suffix keeps
|
|
9
|
+
* the id stable for the MCP server's lifetime (so repeated track_event
|
|
10
|
+
* calls roll up to one synthetic "visitor") while distinguishing
|
|
11
|
+
* separate agent sessions from each other.
|
|
12
|
+
*/
|
|
13
|
+
const MCP_AGENT_VISITOR_ID = `mcp-agent-${Math.random().toString(36).slice(2, 10)}`;
|
|
4
14
|
export const toolDefinitions = {
|
|
5
15
|
plan_experiment: {
|
|
6
16
|
description: `${APEX} — PLANNING ONLY, does not create anything. Analyzes a goal against existing beliefs and experiments, then returns structured options for the user to choose from. After calling, you MUST present the returned options as a numbered list or poll and wait for the user to pick before calling any other Apex tools.`,
|
|
@@ -916,27 +926,40 @@ The Apex Spec ships two peer registries:
|
|
|
916
926
|
|
|
917
927
|
\u2022 **Platform events** (\`APEX_PLATFORM_EVENTS\`) — events Apex's own dashboard fires as merchants use it. Names: \`user_invitation_created\`, \`user_invitation_accepted\`, \`developer_invite_sent\`, \`integration_connected\`, \`integration_sync_completed\`, \`integration_sync_failed\`, \`badge_awarded\`, \`user_level_up\`, \`nudge_scheduled\`, \`drip_step_scheduled\`, \`experiment_launched\`, \`experiment_significant\`, \`experiment_concluded\`, \`experiment_promoted\`, \`portfolio_access_requested\`, \`portfolio_access_approved\`, \`portfolio_access_rejected\`, \`portfolio_share_accepted\`, \`subscription_upgraded\`, \`subscription_downgraded\`, \`tier_limit_approaching\`, \`anomaly_detected\`, \`weekly_digest_scheduled\`, \`cognito_verification_code\`, \`user_signup\`. Use these when an agent is acting *as* the Apex platform (admin tooling, dashboard automation), not as the customer's product.
|
|
918
928
|
|
|
919
|
-
Custom event names are accepted (the spec is open by default), but agents should prefer canonical names from one of the two registries so the event flows directly into the catalog templates, the journey trigger system, and the conversion-goal helpers without manual wiring. The full spec lives at https://docs.apex.inc/spec
|
|
929
|
+
Custom event names are accepted (the spec is open by default), but agents should prefer canonical names from one of the two registries so the event flows directly into the catalog templates, the journey trigger system, and the conversion-goal helpers without manual wiring. The full spec lives at https://docs.apex.inc/spec.
|
|
930
|
+
|
|
931
|
+
Events fired through this tool carry a stable synthetic visitorId (mcp-agent-*, fixed for this MCP process) and a timestamp, so they WILL count toward wiring detection (the same signal the Set up Apex page and get_wiring_status read). They are flagged as agent-fired (source: "mcp" in the event data + the x-apex-source header), so dashboards can distinguish them from real app traffic — but they are NOT test-mode events; fire them deliberately.`,
|
|
920
932
|
schema: z.object({
|
|
921
933
|
event: z.string().describe("Event name — snake_case from APEX_EVENTS or APEX_PLATFORM_EVENTS (e.g. 'page_view', 'in_app_purchase', 'user_invitation_created'). Custom names are accepted but prefer canonical ones."),
|
|
922
934
|
properties: z.record(z.unknown()).optional().describe("Flat snake_case properties matching the spec entry for this event (e.g. { product_id: 'prod_abc', value: 29.99, currency: 'USD' })."),
|
|
923
935
|
}),
|
|
924
936
|
handler: async ({ event, properties }) => {
|
|
937
|
+
// MOBX-006 — the batch path of /api/events reads `visitorId`,
|
|
938
|
+
// `type`, and the event bag (`data`) at the TOP LEVEL of each
|
|
939
|
+
// event. Without a visitorId the event lands in the raw SDK
|
|
940
|
+
// firehose but never reaches the tracking-event store that
|
|
941
|
+
// powers wiring detection / readiness (the route reports it
|
|
942
|
+
// under `untracked`). Stamp the per-process synthetic visitor
|
|
943
|
+
// id + timestamp so agent-fired events actually count.
|
|
925
944
|
const result = await apiPost("/api/events", {
|
|
926
|
-
|
|
945
|
+
workspaceKey: (process.env.APEX_WORKSPACE_KEY || process.env.APEX_PROJECT_KEY) || "default",
|
|
927
946
|
userId: "mcp-agent",
|
|
928
947
|
events: [
|
|
929
948
|
{
|
|
930
949
|
type: event,
|
|
931
|
-
|
|
950
|
+
visitorId: MCP_AGENT_VISITOR_ID,
|
|
932
951
|
timestamp: new Date().toISOString(),
|
|
952
|
+
data: { ...properties, source: "mcp" },
|
|
933
953
|
},
|
|
934
954
|
],
|
|
935
955
|
});
|
|
956
|
+
const wiringNote = result.untracked && result.untracked.length > 0
|
|
957
|
+
? `\nNOT counted toward wiring detection: ${result.untracked[0].reason}`
|
|
958
|
+
: `\nCounts toward wiring detection (visitorId: ${MCP_AGENT_VISITOR_ID}, flagged agent-fired via source: "mcp").`;
|
|
936
959
|
return {
|
|
937
960
|
content: [{
|
|
938
961
|
type: "text",
|
|
939
|
-
text: `Event tracked: "${event}" — ${result.received} event(s) stored.${properties ? `\nProperties: ${JSON.stringify(properties)}` : ""}`,
|
|
962
|
+
text: `Event tracked: "${event}" — ${result.received} event(s) stored.${wiringNote}${properties ? `\nProperties: ${JSON.stringify(properties)}` : ""}`,
|
|
940
963
|
}],
|
|
941
964
|
};
|
|
942
965
|
},
|
|
@@ -989,7 +1012,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
989
1012
|
await apiPost("/api/identity/stitch", {
|
|
990
1013
|
visitorId: `mcp-${email}`,
|
|
991
1014
|
email,
|
|
992
|
-
|
|
1015
|
+
workspaceKey: (process.env.APEX_WORKSPACE_KEY || process.env.APEX_PROJECT_KEY) || "default",
|
|
993
1016
|
metadata: { name, company, ...metadata, source: "mcp" },
|
|
994
1017
|
});
|
|
995
1018
|
return {
|
|
@@ -1004,7 +1027,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1004
1027
|
description: `${APEX} — Read the workspace's canonical Schema (data dictionary): entities, their fields, which fields are wired (observed in live data) vs not, surface coverage, and any canonical mappings. Use to see what variables/attributes a workspace has and what's still unwired before authoring comms or segments.`,
|
|
1005
1028
|
schema: z.object({}),
|
|
1006
1029
|
handler: async () => {
|
|
1007
|
-
const dict = await apiGet("/api/
|
|
1030
|
+
const dict = await apiGet("/api/schema");
|
|
1008
1031
|
const lines = [];
|
|
1009
1032
|
for (const entity of Object.values(dict.entities)) {
|
|
1010
1033
|
lines.push(`\n${entity.label} {{${entity.key}.<field>}}`);
|
|
@@ -1035,7 +1058,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1035
1058
|
.describe("Canonical snake_case name (e.g. 'first_name'), or null to clear"),
|
|
1036
1059
|
}),
|
|
1037
1060
|
handler: async ({ entityKey, fieldName, canonicalName, }) => {
|
|
1038
|
-
const res = await apiPost("/api/
|
|
1061
|
+
const res = await apiPost("/api/schema/map", { entityKey, fieldName, canonicalName: canonicalName ?? null });
|
|
1039
1062
|
const mapped = res.data?.canonicalName;
|
|
1040
1063
|
return {
|
|
1041
1064
|
content: [
|
|
@@ -1101,48 +1124,51 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1101
1124
|
},
|
|
1102
1125
|
},
|
|
1103
1126
|
list_projects: {
|
|
1104
|
-
description: "List all
|
|
1127
|
+
description: "List all workspaces available to your API key. Use this to see which workspaces you can work with.",
|
|
1105
1128
|
schema: z.object({}),
|
|
1106
1129
|
handler: async () => {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1130
|
+
// NOTE: `/api/projects` is the PERMANENT alias path this tool has
|
|
1131
|
+
// shipped against — it returns the flat row shape below. Do not
|
|
1132
|
+
// point at `/api/workspaces` (different response shape).
|
|
1133
|
+
const workspaces = await apiGet("/api/projects");
|
|
1134
|
+
const current = getActiveWorkspace();
|
|
1135
|
+
if (!workspaces.length) {
|
|
1110
1136
|
return {
|
|
1111
1137
|
content: [{
|
|
1112
1138
|
type: "text",
|
|
1113
|
-
text: "No
|
|
1139
|
+
text: "No workspaces found for this API key.",
|
|
1114
1140
|
}],
|
|
1115
1141
|
};
|
|
1116
1142
|
}
|
|
1117
|
-
const lines =
|
|
1118
|
-
const active = p.
|
|
1119
|
-
return ` ${p.
|
|
1143
|
+
const lines = workspaces.map((p) => {
|
|
1144
|
+
const active = p.workspaceKey === current ? " ← active" : "";
|
|
1145
|
+
return ` ${p.workspaceKey} — ${p.name} (${p.url})${active}`;
|
|
1120
1146
|
});
|
|
1121
1147
|
return {
|
|
1122
1148
|
content: [{
|
|
1123
1149
|
type: "text",
|
|
1124
|
-
text: [`
|
|
1150
|
+
text: [`Workspaces (${workspaces.length}):`, ...lines].join("\n"),
|
|
1125
1151
|
}],
|
|
1126
1152
|
};
|
|
1127
1153
|
},
|
|
1128
1154
|
},
|
|
1129
1155
|
switch_project: {
|
|
1130
|
-
description: "Switch the active
|
|
1156
|
+
description: "Switch the active workspace for this session. All subsequent API calls will use this workspace.",
|
|
1131
1157
|
schema: z.object({
|
|
1132
|
-
|
|
1158
|
+
workspaceKey: z.string().describe("The workspace key to switch to"),
|
|
1133
1159
|
}),
|
|
1134
1160
|
handler: async (args) => {
|
|
1135
|
-
|
|
1161
|
+
setActiveWorkspace(args.workspaceKey);
|
|
1136
1162
|
return {
|
|
1137
1163
|
content: [{
|
|
1138
1164
|
type: "text",
|
|
1139
|
-
text: `Switched to
|
|
1165
|
+
text: `Switched to workspace: ${args.workspaceKey}. All subsequent calls will use this workspace.`,
|
|
1140
1166
|
}],
|
|
1141
1167
|
};
|
|
1142
1168
|
},
|
|
1143
1169
|
},
|
|
1144
1170
|
list_orgs: {
|
|
1145
|
-
description: "List all organizations you have access to, with their
|
|
1171
|
+
description: "List all organizations you have access to, with their workspaces. Use this to see which orgs and workspaces are available.",
|
|
1146
1172
|
schema: z.object({}),
|
|
1147
1173
|
handler: async () => {
|
|
1148
1174
|
const ctx = getUserContext();
|
|
@@ -1157,8 +1183,8 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1157
1183
|
const activeOrg = getActiveOrg();
|
|
1158
1184
|
const lines = ctx.orgs.map((org) => {
|
|
1159
1185
|
const active = org.orgId === activeOrg ? " ← active" : "";
|
|
1160
|
-
const
|
|
1161
|
-
return ` ${org.orgName} (${org.orgId}, ${org.role})${active}\n${
|
|
1186
|
+
const workspaces = org.workspaces.map((p) => ` ${p.workspaceKey} — ${p.name}`).join("\n");
|
|
1187
|
+
return ` ${org.orgName} (${org.orgId}, ${org.role})${active}\n${workspaces || " (no workspaces)"}`;
|
|
1162
1188
|
});
|
|
1163
1189
|
return {
|
|
1164
1190
|
content: [{
|
|
@@ -1169,7 +1195,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1169
1195
|
},
|
|
1170
1196
|
},
|
|
1171
1197
|
switch_org: {
|
|
1172
|
-
description: "Switch the active organization. This also switches to the first
|
|
1198
|
+
description: "Switch the active organization. This also switches to the first workspace in that org.",
|
|
1173
1199
|
schema: z.object({
|
|
1174
1200
|
orgId: z.string().describe("The organization ID to switch to (from list_orgs)"),
|
|
1175
1201
|
}),
|
|
@@ -1196,12 +1222,12 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1196
1222
|
};
|
|
1197
1223
|
}
|
|
1198
1224
|
setActiveOrg(args.orgId);
|
|
1199
|
-
const
|
|
1200
|
-
const projectInfo =
|
|
1225
|
+
const workspace = getActiveWorkspace();
|
|
1226
|
+
const projectInfo = workspace ? `, active workspace: ${workspace}` : "";
|
|
1201
1227
|
return {
|
|
1202
1228
|
content: [{
|
|
1203
1229
|
type: "text",
|
|
1204
|
-
text: `Switched to org: ${org.orgName} (${org.
|
|
1230
|
+
text: `Switched to org: ${org.orgName} (${org.workspaces.length} workspace${org.workspaces.length !== 1 ? "s" : ""})${projectInfo}.`,
|
|
1205
1231
|
}],
|
|
1206
1232
|
};
|
|
1207
1233
|
},
|
|
@@ -1322,26 +1348,37 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1322
1348
|
},
|
|
1323
1349
|
},
|
|
1324
1350
|
get_event_taxonomy: {
|
|
1325
|
-
|
|
1351
|
+
// MOBX-002 (post-mortem #9) — this tool used to read the legacy
|
|
1352
|
+
// comms registry (`/api/communications/events`), which disagreed
|
|
1353
|
+
// with the Apex Spec on names AND casing (purchase_completed vs
|
|
1354
|
+
// in_app_purchase, userId vs order_id) and rendered "undefined"
|
|
1355
|
+
// descriptions. There is ONE event vocabulary: the Apex Spec.
|
|
1356
|
+
// This tool now serves it and points agents at get_event_spec.
|
|
1357
|
+
description: "DEPRECATED alias of get_event_spec — the canonical Apex Spec event registry (one vocabulary; the legacy comms taxonomy this used to serve disagreed with it). Prefer get_event_spec.",
|
|
1326
1358
|
schema: z.object({}),
|
|
1327
1359
|
handler: async () => {
|
|
1328
|
-
const
|
|
1329
|
-
const events =
|
|
1330
|
-
|
|
1331
|
-
|
|
1360
|
+
const res = await apiGet("/api/spec/events");
|
|
1361
|
+
const events = res.data?.events ?? {};
|
|
1362
|
+
const names = Object.keys(events);
|
|
1363
|
+
if (names.length === 0) {
|
|
1364
|
+
return { content: [{ type: "text", text: "Spec bundle unavailable. Use get_event_spec." }] };
|
|
1332
1365
|
}
|
|
1333
|
-
const summary =
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1366
|
+
const summary = names
|
|
1367
|
+
.map((n) => `• ${n} — ${events[n]?.description ?? ""}`)
|
|
1368
|
+
.join("\n");
|
|
1369
|
+
return {
|
|
1370
|
+
content: [{
|
|
1371
|
+
type: "text",
|
|
1372
|
+
text: `${names.length} canonical events (Apex Spec ${res.data?.specVersion ?? ""}). Use get_event_spec for full schemas.\n\n${summary}`,
|
|
1373
|
+
}],
|
|
1374
|
+
};
|
|
1338
1375
|
},
|
|
1339
1376
|
},
|
|
1340
1377
|
// ─── Partner Network (MMP-161) ──────────────────────────────────────────
|
|
1341
1378
|
//
|
|
1342
1379
|
// Merchant-scoped tools for managing an affiliate program through the
|
|
1343
1380
|
// Apex Partner Network. Every tool authenticates via the caller's
|
|
1344
|
-
// active
|
|
1381
|
+
// active workspace + API key (same pattern as experiments / comms).
|
|
1345
1382
|
// Partner-facing flows (self-signup, portal) + admin Fraud Ops are
|
|
1346
1383
|
// intentionally not wrapped — they require different auth.
|
|
1347
1384
|
list_partner_programs: {
|
|
@@ -1516,7 +1553,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1516
1553
|
const tag = f.isCustom ? " (custom)" : "";
|
|
1517
1554
|
return `• ${kind}: ${display}${tag} — source: ${f.source}`;
|
|
1518
1555
|
}).join("\n");
|
|
1519
|
-
return { content: [{ type: "text", text: `Fees for ${data.
|
|
1556
|
+
return { content: [{ type: "text", text: `Fees for ${data.workspaceKey}:\n\n${lines}\n\nFull data:\n${JSON.stringify(data, null, 2)}` }] };
|
|
1520
1557
|
},
|
|
1521
1558
|
},
|
|
1522
1559
|
vouch_for_partner: {
|
|
@@ -1536,7 +1573,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1536
1573
|
// Pair with the AudienceActivationWizard (Layer 2) to give agents
|
|
1537
1574
|
// a way to wire up audience instrumentation without leaving the
|
|
1538
1575
|
// editor. Both tools are PROJECT-SCOPED via the api-client's
|
|
1539
|
-
// active-
|
|
1576
|
+
// active-workspace context — the merchant's CLI run pins which
|
|
1540
1577
|
// workspace they're acting on.
|
|
1541
1578
|
wire_audience_seed: {
|
|
1542
1579
|
description: `${APEX} — Wire up the events and traits a campaign audience seed depends on. Use this when the merchant says "wire up the active customers audience" or after the AudienceActivationWizard's Screen 1 hands off to Cursor. Returns the per-event/trait code snippets, the file recommendations, and fires a test event with is_test=1 so the wizard's polling loop confirms the wiring landed end-to-end. Idempotent: calling twice does NOT double-instrument; the agent should still review existing code before adding the snippet.`,
|
|
@@ -1554,13 +1591,13 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1554
1591
|
.describe("When true (default), fires a test event with is_test=1 for each event the seed depends on. The wizard's poll loop sees these and advances. Set to false if the agent only wants to read the snippets without writing test data."),
|
|
1555
1592
|
}),
|
|
1556
1593
|
handler: async ({ seedId, framework = "typescript", fireTestEvent = true, }) => {
|
|
1557
|
-
const
|
|
1558
|
-
if (!
|
|
1594
|
+
const workspaceKey = getActiveWorkspace() || (process.env.APEX_WORKSPACE_KEY || process.env.APEX_PROJECT_KEY);
|
|
1595
|
+
if (!workspaceKey) {
|
|
1559
1596
|
return {
|
|
1560
1597
|
content: [
|
|
1561
1598
|
{
|
|
1562
1599
|
type: "text",
|
|
1563
|
-
text: "No active
|
|
1600
|
+
text: "No active workspace. Call set_active_project first or set APEX_PROJECT_KEY.",
|
|
1564
1601
|
},
|
|
1565
1602
|
],
|
|
1566
1603
|
};
|
|
@@ -1622,7 +1659,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1622
1659
|
? `## Test events fired\nFired test events for: ${firedTests.join(", ")}. The wizard's polling loop will see these and advance. Confirm the merchant fires REAL events from production code paths before relying on the audience.`
|
|
1623
1660
|
: `## Test events\nNo test events fired (per fireTestEvent=false). Run the wizard's "Send a test event" button manually after wiring.`,
|
|
1624
1661
|
``,
|
|
1625
|
-
`
|
|
1662
|
+
`Workspace: ${workspaceKey}`,
|
|
1626
1663
|
].join("\n");
|
|
1627
1664
|
return { content: [{ type: "text", text: summary }] };
|
|
1628
1665
|
},
|
|
@@ -1643,13 +1680,13 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1643
1680
|
.describe("When true (default), fires a test event with is_test=1 for each NON-AUTO-FIRED event the audience depends on. Auto-fired events (pageview, click, etc.) are skipped — they're already firing automatically and don't need a synthetic test."),
|
|
1644
1681
|
}),
|
|
1645
1682
|
handler: async ({ audienceId, framework = "typescript", fireTestEvent = true, }) => {
|
|
1646
|
-
const
|
|
1647
|
-
if (!
|
|
1683
|
+
const workspaceKey = getActiveWorkspace() || (process.env.APEX_WORKSPACE_KEY || process.env.APEX_PROJECT_KEY);
|
|
1684
|
+
if (!workspaceKey) {
|
|
1648
1685
|
return {
|
|
1649
1686
|
content: [
|
|
1650
1687
|
{
|
|
1651
1688
|
type: "text",
|
|
1652
|
-
text: "No active
|
|
1689
|
+
text: "No active workspace. Call set_active_project first or set APEX_PROJECT_KEY.",
|
|
1653
1690
|
},
|
|
1654
1691
|
],
|
|
1655
1692
|
};
|
|
@@ -1718,7 +1755,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1718
1755
|
else {
|
|
1719
1756
|
sections.push(`\n## Test events\nNo test events fired (per fireTestEvent=false). Run the wizard's "Send a test event" button manually after wiring.`);
|
|
1720
1757
|
}
|
|
1721
|
-
sections.push(`\
|
|
1758
|
+
sections.push(`\nWorkspace: ${workspaceKey}`);
|
|
1722
1759
|
return { content: [{ type: "text", text: sections.join("\n") }] };
|
|
1723
1760
|
},
|
|
1724
1761
|
},
|
|
@@ -1730,13 +1767,13 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1730
1767
|
.describe("Audience id (the workspace's audience record, not the seed). Use list_audiences first to find it."),
|
|
1731
1768
|
}),
|
|
1732
1769
|
handler: async ({ audienceId }) => {
|
|
1733
|
-
const
|
|
1734
|
-
if (!
|
|
1770
|
+
const workspaceKey = getActiveWorkspace() || (process.env.APEX_WORKSPACE_KEY || process.env.APEX_PROJECT_KEY);
|
|
1771
|
+
if (!workspaceKey) {
|
|
1735
1772
|
return {
|
|
1736
1773
|
content: [
|
|
1737
1774
|
{
|
|
1738
1775
|
type: "text",
|
|
1739
|
-
text: "No active
|
|
1776
|
+
text: "No active workspace. Call set_active_project first or set APEX_PROJECT_KEY.",
|
|
1740
1777
|
},
|
|
1741
1778
|
],
|
|
1742
1779
|
};
|
|
@@ -1765,7 +1802,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1765
1802
|
preview && preview.matchingCount === 0
|
|
1766
1803
|
? `\n## Diagnosis\nZero matches. Most likely causes:\n 1. Events the predicate references aren't firing yet from the merchant's codebase. Run \`wire_audience_seed\` if this is a starter audience, or check the predicate's event names against your codebase.\n 2. The predicate's window (\`last_n_days\`, \`ever\`) is too narrow.\n 3. End-user identity isn't being stitched. Each \`apex.track()\` call must include \`endUserId\` in its data payload, or be preceded by an \`identify()\` call.\n`
|
|
1767
1804
|
: "",
|
|
1768
|
-
`\
|
|
1805
|
+
`\nWorkspace: ${workspaceKey}`,
|
|
1769
1806
|
].join("\n");
|
|
1770
1807
|
return { content: [{ type: "text", text: summary }] };
|
|
1771
1808
|
},
|
|
@@ -1934,7 +1971,7 @@ Custom event names are accepted (the spec is open by default), but agents should
|
|
|
1934
1971
|
description: `${APEX} — READ-ONLY. Returns the current Set up Apex state for the active workspace: which steps the merchant has marked complete or skipped, plus the live signal flags (snippet installed, mobile live, sender domain verified, etc.). Use to answer "where am I in onboarding?" or to surface "your next step is X" recommendations. WRITE tools (complete_setup_step, skip_setup_step) are deliberately NOT exposed pending a server-enforced confirmation nonce.`,
|
|
1935
1972
|
schema: z.object({}),
|
|
1936
1973
|
handler: async () => {
|
|
1937
|
-
// The active
|
|
1974
|
+
// The active workspace key is encoded in the x-apex-workspace header
|
|
1938
1975
|
// that apiGet sends automatically.
|
|
1939
1976
|
const data = await apiGet("/api/setup-state");
|
|
1940
1977
|
const { overrides, signal, workspaceKey } = data.data;
|
|
@@ -1973,7 +2010,7 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
1973
2010
|
.describe("Include archived sources. Defaults to false."),
|
|
1974
2011
|
}),
|
|
1975
2012
|
handler: async ({ includeArchived }) => {
|
|
1976
|
-
const proj =
|
|
2013
|
+
const proj = getActiveWorkspace();
|
|
1977
2014
|
const qs = includeArchived ? "?includeArchived=true" : "";
|
|
1978
2015
|
const json = await apiGet(`/api/workspaces/${proj}/data-sources${qs}`);
|
|
1979
2016
|
const sources = json.data ?? [];
|
|
@@ -1993,7 +2030,7 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
1993
2030
|
name: z.string().describe("Display name, e.g. 'Marketing site' or 'Backend API'."),
|
|
1994
2031
|
}),
|
|
1995
2032
|
handler: async ({ kind, name }) => {
|
|
1996
|
-
const proj =
|
|
2033
|
+
const proj = getActiveWorkspace();
|
|
1997
2034
|
const json = await apiPost(`/api/workspaces/${proj}/data-sources`, { kind, name });
|
|
1998
2035
|
const s = json.data;
|
|
1999
2036
|
return { content: [{ type: "text", text: `✓ Created data source **${s.name}** (${s.kind}) — ${s.status} · \`${s.id}\`` }] };
|
|
@@ -2005,7 +2042,7 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
2005
2042
|
id: z.string().describe("The data source id (ds_...)."),
|
|
2006
2043
|
}),
|
|
2007
2044
|
handler: async ({ id }) => {
|
|
2008
|
-
const proj =
|
|
2045
|
+
const proj = getActiveWorkspace();
|
|
2009
2046
|
const json = await apiGet(`/api/workspaces/${proj}/data-sources/${encodeURIComponent(id)}/health`);
|
|
2010
2047
|
const h = json.data;
|
|
2011
2048
|
return { content: [{ type: "text", text: `# ${h.kind} source ${h.id}\n\n- status: **${h.status}**\n- first seen: ${h.firstSeenAt ?? "never"}\n- last seen: ${h.lastSeenAt ?? "never"}` }] };
|
|
@@ -2041,5 +2078,109 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
2041
2078
|
return { content: [{ type: "text", text: hosts.length ? `# Hostnames (${hosts.length})\n\n${lines.join("\n")}` : "No hostnames seen yet." }] };
|
|
2042
2079
|
},
|
|
2043
2080
|
},
|
|
2081
|
+
get_event_spec: {
|
|
2082
|
+
description: `${APEX} — The canonical event reference for instrumenting Apex tracking: every event name, its fields (with required/optional markers), and example payloads, as a JSON Schema bundle. Call this BEFORE wiring apex.track()/Apex.track() calls or Server Events API posts into a codebase so you use canonical names and field shapes. Pair with get_wiring_status to verify your instrumentation afterwards.`,
|
|
2083
|
+
schema: z.object({
|
|
2084
|
+
event: z.string().optional().describe("Return only this event's schema (canonical name, e.g. 'add_to_cart'). Omit for the full bundle."),
|
|
2085
|
+
}),
|
|
2086
|
+
handler: async ({ event }) => {
|
|
2087
|
+
const res = await apiGet("/api/spec/events");
|
|
2088
|
+
const bundle = res.data ?? {};
|
|
2089
|
+
if (event) {
|
|
2090
|
+
const all = (bundle.events ?? {});
|
|
2091
|
+
const match = all[event];
|
|
2092
|
+
if (!match) {
|
|
2093
|
+
const names = Object.keys(all).slice(0, 50).join(", ");
|
|
2094
|
+
return {
|
|
2095
|
+
content: [{ type: "text", text: `Unknown event "${event}". Known events include: ${names}` }],
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
return {
|
|
2099
|
+
content: [{ type: "text", text: `# ${event}\n\n\`\`\`json\n${JSON.stringify(match, null, 2)}\n\`\`\`` }],
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
return {
|
|
2103
|
+
content: [{ type: "text", text: `# Apex event reference\n\n\`\`\`json\n${JSON.stringify(bundle, null, 2)}\n\`\`\`` }],
|
|
2104
|
+
};
|
|
2105
|
+
},
|
|
2106
|
+
},
|
|
2107
|
+
get_wiring_status: {
|
|
2108
|
+
description: `${APEX} — Live wiring verification for the active workspace: which canonical events have been received (tier: waiting → detected → verified), which identify traits (first_name, email, …) are arriving, per-data-source status, and which journey/communication templates are Ready to run. Call this AFTER instrumenting tracking code to verify your own work — iterate until each wired event reports "detected". Presence-only: never returns end-user values.`,
|
|
2109
|
+
schema: z.object({}),
|
|
2110
|
+
handler: async () => {
|
|
2111
|
+
const workspaceKey = getActiveWorkspace();
|
|
2112
|
+
if (!workspaceKey) {
|
|
2113
|
+
return {
|
|
2114
|
+
content: [{ type: "text", text: "No active workspace. Use switch_project first (or set APEX_PROJECT_KEY)." }],
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
const json = await apiGet(`/api/workspaces/${encodeURIComponent(workspaceKey)}/readiness`);
|
|
2118
|
+
const d = json.data;
|
|
2119
|
+
const eventLines = d.events.map((e) => `- ${e.name}: ${e.tier} (${e.count.toLocaleString()} events, platforms: ${e.platforms.join("/") || "—"})${e.observedFields?.length ? ` — fields seen: ${e.observedFields.join(", ")}` : ""}`);
|
|
2120
|
+
const traitLines = d.traits.map((t) => `- ${t.name}: ${t.present ? "arriving" : "not seen"}${t.inferredType ? ` (${t.inferredType})` : ""}`);
|
|
2121
|
+
const sourceLines = d.sources.map((s) => `- ${s.name} (${s.kind}): ${s.status}${s.lastSeenAt ? ` — last event ${s.lastSeenAt}` : ""}`);
|
|
2122
|
+
const readyTemplates = d.templates.filter((t) => t.ready);
|
|
2123
|
+
const blockedTemplates = d.templates.filter((t) => !t.ready);
|
|
2124
|
+
const templateLines = [
|
|
2125
|
+
...readyTemplates.map((t) => `- ✅ ${t.title} (${t.templateKind}) — Ready to run`),
|
|
2126
|
+
...blockedTemplates.slice(0, 10).map((t) => `- ⏳ ${t.title} (${t.templateKind}) — needs ${[...t.missingEvents, ...t.missingTraits].join(", ") || "?"}`),
|
|
2127
|
+
];
|
|
2128
|
+
const milestones = [
|
|
2129
|
+
d.milestones.firstEventAt ? `First event: ${d.milestones.firstEventAt}` : "First event: not yet",
|
|
2130
|
+
d.milestones.firstIdentifiedAt ? `First identified customer: ${d.milestones.firstIdentifiedAt}` : "First identified customer: not yet",
|
|
2131
|
+
];
|
|
2132
|
+
return {
|
|
2133
|
+
content: [{
|
|
2134
|
+
type: "text",
|
|
2135
|
+
text: `# Wiring status — ${workspaceKey}\n\n## Milestones\n${milestones.map((m) => `- ${m}`).join("\n")}\n\n## Events received\n${eventLines.join("\n") || "_none yet_"}\n\n## Identify traits\n${traitLines.join("\n") || "_none yet_"}\n\n## Data sources\n${sourceLines.join("\n") || "_none yet_"}\n\n## Templates\n${templateLines.join("\n") || "_none derivable yet_"}`,
|
|
2136
|
+
}],
|
|
2137
|
+
};
|
|
2138
|
+
},
|
|
2139
|
+
},
|
|
2140
|
+
propose_wiring_plan: {
|
|
2141
|
+
description: `${APEX} — Report the Apex Spec instrumentation opportunities you found by SCANNING the merchant's codebase (auth flows → identify + user_signed_up, checkout → purchase events, forms → form_submit, app purchases → in_app_purchase, server order paths → order_placed, …). Call this BEFORE wiring: the plan appears on the merchant's setup page as pending lights, each flipping green when its first real event arrives. Structure your scan report in three sections: (1) ALREADY WIRED — events the repo already instruments (include them here with wired: true; they're awaiting their first heartbeat), (2) GAPS TO WIRE — the work you propose, (3) WON'T WIRE + WHY — opportunities you're deliberately skipping (e.g. a server event with no backend, or a form that already fires a richer dedicated event). Then instrument each gap where the truth lives and verify with get_wiring_status. Scan first; never ask the merchant to enumerate events for you.`,
|
|
2142
|
+
schema: z.object({
|
|
2143
|
+
events: z
|
|
2144
|
+
.array(z.object({
|
|
2145
|
+
name: z
|
|
2146
|
+
.string()
|
|
2147
|
+
.describe("Canonical snake_case event name from the Apex Spec (use get_event_spec), or a custom snake_case name."),
|
|
2148
|
+
surface: z
|
|
2149
|
+
.enum(["web", "mobile", "server"])
|
|
2150
|
+
.describe("Where this event should fire FROM — wire each event where the truth lives."),
|
|
2151
|
+
note: z
|
|
2152
|
+
.string()
|
|
2153
|
+
.optional()
|
|
2154
|
+
.describe("Short pointer to where you found the opportunity, e.g. 'checkout/confirm.ts handlePayment()'."),
|
|
2155
|
+
wired: z
|
|
2156
|
+
.boolean()
|
|
2157
|
+
.optional()
|
|
2158
|
+
.describe("True when the repo ALREADY instruments this event (awaiting its first heartbeat) — the merchant's board labels it 'wired' instead of 'agent plan'."),
|
|
2159
|
+
}))
|
|
2160
|
+
.min(1)
|
|
2161
|
+
.describe("Every instrumentation opportunity found in the scan — both already-wired events (wired: true) and the gaps you propose to wire."),
|
|
2162
|
+
summary: z.string().optional().describe("One-paragraph scan summary for the merchant — include what you're deliberately NOT wiring and why."),
|
|
2163
|
+
}),
|
|
2164
|
+
handler: async (args) => {
|
|
2165
|
+
const workspaceKey = getActiveWorkspace();
|
|
2166
|
+
if (!workspaceKey) {
|
|
2167
|
+
return {
|
|
2168
|
+
content: [{ type: "text", text: "No active workspace. Use switch_project first (or set APEX_PROJECT_KEY)." }],
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
const json = await apiPost(`/api/workspaces/${encodeURIComponent(workspaceKey)}/wiring-plan`, {
|
|
2172
|
+
events: args.events,
|
|
2173
|
+
summary: args.summary,
|
|
2174
|
+
source: "mcp",
|
|
2175
|
+
});
|
|
2176
|
+
const lines = json.data.events.map((e) => `- ${e.name} (${e.surface})${e.wired ? " — already wired, awaiting first heartbeat" : ""}`);
|
|
2177
|
+
return {
|
|
2178
|
+
content: [{
|
|
2179
|
+
type: "text",
|
|
2180
|
+
text: `# Wiring plan proposed — ${workspaceKey}\n\nThe merchant's setup page now shows ${json.data.events.length} pending event light${json.data.events.length === 1 ? "" : "s"}:\n${lines.join("\n")}\n\nNext: instrument each gap where it happens in the code (smallest change, never commit), then call get_wiring_status until every light reports "detected".`,
|
|
2181
|
+
}],
|
|
2182
|
+
};
|
|
2183
|
+
},
|
|
2184
|
+
},
|
|
2044
2185
|
};
|
|
2045
2186
|
//# sourceMappingURL=tools.js.map
|