Skip to content

Commit 93baa26

Browse files
author
clamp-bot
committed
sync from monorepo @ 1c86981
1 parent bedaead commit 93baa26

2 files changed

Lines changed: 108 additions & 26 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,11 @@ Get your API key from the Clamp dashboard under **Settings → API Keys**. Keys
104104
| `pages.engagement` | Per-page metrics with `view`: `summary`, `engagement` (adds engagement seconds and bounce rate), `sections` (per-section view counts for one pathname; needs the section-views SDK extension). |
105105
| `events.list` | Custom event counts with property filtering and grouping. |
106106
| `events.observed_schema` | The actual fired-event signature with per-property type observations. Diff against a local `event-schema.yaml` to surface schema drift. |
107+
| `events.property_values` | Top distinct string-typed values a property has taken on a specific event. Use to discover the value space before defining a cohort filter or running `events.list` with `property=`. |
107108
| `revenue.sum` | Sum revenue from Money-typed event properties. Split by currency, optionally grouped by any dimension. |
108109
| `sessions.paths` | Aggregate session paths: top entry → exit pairs with pages per session and duration. |
109110
| `users.journey` | Chronological session-and-event reconstruction for one anonymous ID. |
110-
| `cohorts.create` / `cohorts.list` / `cohorts.retention` / `cohorts.compare` | Define cohorts by event + period + filter; query retention curves; compare two cohorts side-by-side. |
111+
| `cohorts.create` / `cohorts.list` / `cohorts.retention` / `cohorts.compare` | Define cohorts by event + period + filter; query retention curves; compare 2–10 cohorts side-by-side on the same retention windows. |
111112
| `errors.list` / `errors.groups` / `errors.timeline` / `errors.context` | Recent errors, fingerprint-grouped errors with affected-user counts, error rate over time, and breadcrumbs leading to a single error. |
112113
| `projects.list` | List all projects this credential can access. |
113114
| `docs.search` | Keyword-search the Clamp docs index. |

src/tools.ts

Lines changed: 106 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,18 @@ const eventsObservedSchemaOutput = {
255255
events: z.array(observedEventSchema),
256256
};
257257

258+
const propertyValueRowSchema = z.object({
259+
value: z.string(),
260+
count: z.number(),
261+
});
262+
263+
const eventsPropertyValuesOutput = {
264+
event: z.string(),
265+
property: z.string(),
266+
period: z.string(),
267+
values: z.array(propertyValueRowSchema),
268+
};
269+
258270
const revenueRowSchema = z.object({
259271
group_value: z.string().optional(),
260272
currency: z.string(),
@@ -447,8 +459,8 @@ const cohortsListOutput = {
447459

448460
const retentionPointSchema = z.object({
449461
period: z.string(),
450-
count: z.number(),
451-
rate: z.string(),
462+
retained: z.number(),
463+
rate: z.number(),
452464
});
453465

454466
const cohortsRetentionOutput = {
@@ -458,16 +470,13 @@ const cohortsRetentionOutput = {
458470
};
459471

460472
const cohortsCompareOutput = {
461-
a: z.object({
462-
cohort: z.string(),
463-
size: z.number(),
464-
retention: z.array(retentionPointSchema),
465-
}),
466-
b: z.object({
467-
cohort: z.string(),
468-
size: z.number(),
469-
retention: z.array(retentionPointSchema),
470-
}),
473+
cohorts: z.array(
474+
z.object({
475+
cohort: z.string(),
476+
size: z.number(),
477+
retention: z.array(retentionPointSchema),
478+
}),
479+
),
471480
};
472481

473482
const okOutput = { ok: z.boolean() };
@@ -841,6 +850,70 @@ Pairs with: \`events.list\` for per-event volume context (this tool also returns
841850
},
842851
);
843852

853+
// ── Tool: events.property_values ───────────────────
854+
server.registerTool(
855+
"events.property_values",
856+
{
857+
description: `Top distinct *values* a string-typed property has taken on a specific event in the period, ranked by occurrence count. Useful when you need to know the value space before filtering — e.g. cohort definitions, segment filters, "what values does \`plan\` take on \`signup\`?".
858+
859+
Response shape:
860+
\`\`\`
861+
{
862+
event: "signup",
863+
property: "plan",
864+
period: "30d",
865+
values: [
866+
{ value: "free", count: 412 },
867+
{ value: "pro", count: 138 },
868+
{ value: "growth", count: 22 }
869+
]
870+
}
871+
\`\`\`
872+
873+
Examples:
874+
- "what plans do signups come from" → event="signup", property="plan"
875+
- "which countries hit the checkout event" → event="checkout", property="country" (only if you instrument it as a property; otherwise use traffic.breakdown)
876+
- "what \`source\` values does the lead event carry" → event="lead", property="source"
877+
878+
Limitations: STRING properties only — same scope as cohort filter values (cohort filtering is string-equality only). Numeric / boolean / money properties are out of scope. Results are capped at \`limit\` (default 50, max 200) so very high-cardinality properties (user_id, session_id, URLs) are truncated to the top-N by count. Events without the property at all are skipped — only rows where the property is present and non-empty contribute.
879+
880+
Pairs with: \`events.observed_schema\` to discover which properties an event actually fires; \`cohorts.create\` to scope a cohort by a specific value of those.`,
881+
inputSchema: {
882+
project_id: projectIdParam,
883+
event: z
884+
.string()
885+
.min(1)
886+
.max(200)
887+
.describe(
888+
'Event name (e.g. "signup", "checkout_completed"). Required — values are scoped to one event at a time.',
889+
),
890+
property: z
891+
.string()
892+
.min(1)
893+
.max(128)
894+
.describe(
895+
'Property key on the event (e.g. "plan", "source"). Required. Only string-typed properties are surfaced; pass a numeric/boolean property name and you\'ll get an empty `values` array.',
896+
),
897+
period: periodParam,
898+
limit: z
899+
.coerce
900+
.number()
901+
.int()
902+
.min(1)
903+
.max(200)
904+
.optional()
905+
.describe("Max distinct values to return, ranked by count desc (1-200). Defaults to 50."),
906+
},
907+
outputSchema: eventsPropertyValuesOutput,
908+
annotations: { readOnlyHint: true },
909+
},
910+
async ({ project_id, ...rest }) => {
911+
const p = resolveProject(project_id);
912+
if (isErr(p)) return p;
913+
return out(await api(`/analytics/${p.projectId}/property-values${qs(rest)}`));
914+
},
915+
);
916+
844917
// ── Tool: revenue.sum ──────────────────────────────
845918
server.registerTool(
846919
"revenue.sum",
@@ -1285,7 +1358,7 @@ Limitations:
12851358
- Membership is computed at query time, so very large cohorts cost on every retention call.
12861359
- Names must be lowercase alphanumeric with hyphens / underscores.
12871360
1288-
Pairs with: \`cohorts.retention\` to read the curve once the cohort is created; \`cohorts.compare\` for two-cohort side-by-side reads; \`cohorts.list\` to discover existing cohort handles.`,
1361+
Pairs with: \`events.property_values\` to discover valid filter values before creating; \`cohorts.retention\` to read the curve once the cohort is created; \`cohorts.compare\` to stack 2–10 cohorts side-by-side; \`cohorts.list\` to discover existing cohort handles.`,
12891362
inputSchema: {
12901363
project_id: projectIdParam,
12911364
name: z
@@ -1371,7 +1444,7 @@ Examples:
13711444
13721445
Limitations: retention measures any non-pageview-end event presence. Custom retention metrics (e.g. "retained = fired purchase event") are not in 0.x. The numerator is computed per-day, so windows like "30d" return only day-30 activity. Cohort size is the denominator and is included in the response so consumers can apply sample-size discipline.
13731446
1374-
Pairs with: \`cohorts.compare\` for two-cohort side-by-side; \`cohorts.list\` to discover available cohort names.`,
1447+
Pairs with: \`cohorts.compare\` to stack 2–10 cohorts side-by-side at the same windows; \`cohorts.list\` to discover available cohort names.`,
13751448
inputSchema: {
13761449
project_id: projectIdParam,
13771450
name: z.string().min(1).describe("Cohort name to query."),
@@ -1394,22 +1467,30 @@ Pairs with: \`cohorts.compare\` for two-cohort side-by-side; \`cohorts.list\` to
13941467
server.registerTool(
13951468
"cohorts.compare",
13961469
{
1397-
description: `Compare two saved cohorts side-by-side on retention. Returns each cohort's size and retention curve over the same period set, so you can read "did this week's signups retain better than last week's?" or "is this experiment cohort behaving differently than control?" without composing the rates manually.
1470+
description: `Compare 2–10 saved cohorts side-by-side on retention. Returns each cohort's size and retention curve over the same period set, so you can read "did this week's signups retain better than last week's?", "is this experiment cohort behaving differently than control?", or stack a few quarters' cohorts to spot a structural retention trend — without composing the rates manually.
1471+
1472+
Order matters: the first name is treated as the primary; downstream renderings (dashboard, summaries) read deltas relative to it. Duplicates are silently deduplicated.
13981473
13991474
Examples:
1400-
- "did April 14 signups retain better than April 7" → a="signups_apr_14", b="signups_apr_07"
1401-
- "are pro-plan signups stickier than free" → a="pro_signups_q2", b="free_signups_q2"
1402-
- "compare two onboarding variants out to 4 weeks" → a="onboarding_v1", b="onboarding_v2", periods="1w,2w,4w"
1475+
- "did April 14 signups retain better than April 7" → names="signups_apr_14,signups_apr_07"
1476+
- "are pro-plan signups stickier than free" → names="pro_signups_q2,free_signups_q2"
1477+
- "compare two onboarding variants out to 4 weeks" → names="onboarding_v1,onboarding_v2", periods="1w,2w,4w"
1478+
- "three-arm experiment retention" → names="control,variant_a,variant_b"
1479+
- "is signup retention improving quarter-over-quarter" → names="signups_q1,signups_q2,signups_q3,signups_q4"
14031480
1404-
Limitations: only two cohorts at a time. The same retention windows are applied to both — there's no way to use different windows per side. Sample-size caveats apply per cohort; check both \`size\` values before reading the rate delta.`,
1481+
Limitations: at most 10 cohorts per call. The same retention windows are applied to every cohort — there's no way to use different windows per slot. Sample-size caveats apply per cohort; check each \`size\` value before reading rate deltas (small cohorts make differences look meaningful when they aren't). Stacking many cohorts increases the multiple-comparisons risk — a divergent-looking row in a 5-way compare may just be random variation; commit to a hypothesis before reading.`,
14051482
inputSchema: {
14061483
project_id: projectIdParam,
1407-
a: z.string().min(1).describe("Name of the first cohort."),
1408-
b: z.string().min(1).describe("Name of the second cohort."),
1409-
periods: z
1410-
.string()
1411-
.optional()
1412-
.describe('Comma-separated retention windows, same format as cohorts.retention.'),
1484+
names: z
1485+
.string()
1486+
.min(1)
1487+
.describe(
1488+
'Comma-separated cohort names, 2–10 entries. First name is treated as the primary. Example: "signups_apr_14,signups_apr_07" or "control,variant_a,variant_b".',
1489+
),
1490+
periods: z
1491+
.string()
1492+
.optional()
1493+
.describe('Comma-separated retention windows, same format as cohorts.retention.'),
14131494
},
14141495
outputSchema: cohortsCompareOutput,
14151496
annotations: { readOnlyHint: true },

0 commit comments

Comments
 (0)