| name | build-charts-with-typed-params |
| description | Builds Amplitude charts using the typed `chart` parameter on query_amplitude_data — events, filters, group-bys, segments, date ranges, and per-kind fields for segmentation, funnel, retention, sessions, and data tables. Use when creating a chart, modifying or forking a saved one, or when a query returns empty data. |
| x-amp-flags | ["mcp-consolidate-charts"] |
Build Charts With Typed Params
Express the chart as typed, UI-shaped parameters and let the server compile it.
Do not hand-build raw definition JSON.
Typed chart first
query_amplitude_data takes exactly one of chart or definition.
chart (preferred) — a small model mirroring the chart builder UI. It is
compiled server-side by Langley's CompileChart into a validated definition,
so a successful compile is structurally correct by construction. Compile
errors name the offending field and come back with a fix-oriented hint.
definition (fallback) — raw definition JSON. Only for chart types the
typed model does not cover (composition, revenueLtv) or advanced params
with no typed field.
Field names are snake_case; they are validated server-side by Pydantic.
The one thing that goes wrong
The compiler validates structure, not your taxonomy. An event or property
name that doesn't exist won't error — it returns a well-formed chart with
empty data, which reads like a real answer of "zero".
Resolve names first with search_amp_data_taxonomy, and get_properties
(propertyType: 'event', 'user', or 'group'). Use the exact name and
scope that comes back. There is no get_event_properties tool — server
descriptions and error messages sometimes mention it; use get_properties
instead.
Two workflows
Create: search_amp_data_taxonomy for the taxonomy → build the typed chart → call
query_amplitude_data → render_amplitude_chart with the returned
chartEditId to show it.
Modify or fork a saved chart: search_amp_entities to find the chart id →
get_amplitude_charts with include: 'typed' → edit the returned object →
call query_amplitude_data with that chart and chartId set to the saved
chart id. Passing chartId links the edit to its parent, and the parent's
params fill any gaps the typed model omits. Reading a real chart back as typed
params is also the fastest way to see how this project spells things.
get_amplitude_charts has five modes on include: link (default; validates
ids, returns URLs, doesn't run the chart), typed, definition (raw), data
(runs it, max 3 ids), and guide (schema for the raw fallback). All except
guide need concrete ids — resolve them via search_amp_entities or get_from_url first.
Building blocks
These are shared across every chart kind.
Condition — one filter (+ Filter by, or a segment condition):
{ "property": "platform", "op": "is", "values": ["iOS"], "scope": "event" }
op: is, is not, contains, does not contain, greater than,
less than, set, is not null. Use contains for prefix/substring matching.
For presence checks prefer set — is not null is rejected by some chart
kinds (data_table segments) even though error hints suggest it; set works
everywhere. scope: event, user, group,
session, or derivedV2 for computed properties — take the scope from the
taxonomy lookup rather than guessing. Group-scoped properties also need
group_type (see below).
Event — { event, where[], group_by[] }. A composite event puts several
events in one slot: add object_type as INLINE_CUSTOM_EVENT (any member
counts) or COMPARISON_EVENT (members compared), plus members[].
Segment — a population, combining property conditions and behaviors:
{
"where": [{ "property": "country", "op": "is", "values": ["United States"], "scope": "user" }],
"performed": [{ "event": "Purchase", "op": ">=", "count": 1,
"time_type": "rolling", "time_value": 30 }]
}
Omit segments entirely for all users. In performed, time_type defaults to
forEachInterval; use rolling with time_value as lookback days.
Date range — required, and either relative or absolute, never both:
{ "relative": "Last 30 Days" }
{ "start": "2026-01-01", "end": "2026-01-31" }
{ "start": "now-90d" }
Optional timezone is an IANA name; omit for the project default.
The relative range's unit must match the interval: with weekly interval
write "Last 12 Weeks", monthly "Last 6 Months" — "Last 3 Years" with a
weekly interval is rejected. Re-denominate the window in the interval's unit
or change the interval to match.
Interval — a word, not a number: hour, day (default), week, month,
quarter. Sub-daily intervals only allow short windows (hour caps around
8 days).
count_unique_by — the counting entity, "User" by default, or a group
like "org id".
Group-scoped properties
Group properties describe the account, not the user or the event — plan
tier, ARR, industry, on the org id (or similarly named) group type. They are
how almost every B2B question gets asked, and they are the most common thing to
get stuck on.
Discover them explicitly — they are not in the event or user catalogues:
get_properties({ projectId, propertyType: "group", groupType: "org id" })
Then use scope: "group" with the group_type the lookup returned, in a
where filter or a group_by:
{ "property": "plan", "op": "is", "values": ["Enterprise"],
"scope": "group", "group_type": "org id" }
Three rules that avoid nearly all of the failures seen in production:
group_type is the group's display name ("org id", "company") —
exactly as get_group_types / get_properties spells it, lowercase and
spaced. Not the property name, not "Group", not a grp:-prefixed key.
- Never prefix the property name. Write
"plan", not "grp:plan" or
"gp:org id:plan" — those are storage-layer spellings that the chart API
rejects.
- Counting by account is a different setting. To count organizations
rather than users, set
count_unique_by: "org id". That is independent of
whether you filter or break down by a group property.
When a group property is rejected
Invalid group property <name> for group type <type> means the backend's
property registry has no key for that name — the property is advertised by the
taxonomy API but not queryable. This is a data-plane gap, not a mistake in
your query, and retrying spelling variants will not fix it. Retry loops
burning ten-plus calls on this are the single largest source of wasted turns on
this tool.
Do this instead, in order:
- Re-read the exact name from
get_properties({propertyType: 'group'}). If it
differs from what you sent, correct it and retry once.
- If it matches, stop retrying. Check for a user-scoped equivalent (accounts
are often mirrored onto users, e.g. a user property
plan) and use
scope: "user".
- If there is no equivalent, say plainly that the group property is not
queryable in this project and give the user the answer you can produce —
usually the same chart with
count_unique_by: "org id" and no group
breakdown.
Chart kinds
kind discriminates the union. All kinds take group_by, segments,
date_range, interval, count_unique_by, and name (always set a
descriptive name — it becomes the title).
segmentation
Needs events (at least one). measured_as.as defaults to unique_users;
other values are event_totals, active_pct, avg_per_user, frequency,
formula, histogram, and the property aggregations property_sum,
property_avg, property_min, property_max, property_median,
property_count, property_count_avg.
Property aggregations and histogram require the aggregated property to also
appear in that event's group_by — omitting it is a common cause of an empty
or malformed result.
For formula, put the expression in measured_as.formula using UPPERCASE
functions (UNIQUES, TOTALS, PROPSUM, PERCENTILE, …) and refer to events
as A, B, C in the order they are listed.
Also available: rolling_window (days), cumulative, period_over_period
(period, parent, grandparent, quarter), and vis
(line, bar, area, stackedbar, pie, kpi).
funnel
Needs steps (at least two). conversion_window is {value, unit} where unit
is second, minute, hour, day, or week; a bare number or "7 days"
also works, and omitting it gives the product default of 30 days. Set it
explicitly whenever the request implies a window.
mode is ordered (default), unordered, or sequential. measured_as.as
is conversion (default), conversion_over_time, time_to_convert,
time_to_convert_over_time, or step_count.
constant_properties forces values to match across steps — this is how you
build a same-session funnel (hold session_id). excluded_events takes
{event, step_index}, where -1 excludes globally across all steps.
retention
Needs start_event (use _new for new users) and return_events (OR-combined;
use _active for any activity). retention_method is rolling (default; on or
after day N), nday (exactly day N), bracket (custom, via
retention_brackets like [[0,7],[7,14]]), or nday_or_before.
measured_as is retention or usage_interval.
sessions
measured_as is one of totalSessions (default), average, length,
peruser, totalTime, averageTimePerUser, averageEventsPerSession,
totalEvents, eventCountDistribution, or formula. Use groups for multiple
series; omit it for a single unfiltered series.
data_table
Needs columns (at least one), each with a metric_type — UNIQUES, TOTALS,
FORMULA, SESSIONS, CONVERSION, or the property aggregations PROPSUM,
PROPAVG, PROPMAX, PROPMIN. rows are the breakdown dimensions, each
kind either property or time.
Worked example
Weekly unique users of a signup event, on iOS, among users in the US, broken out
by plan:
{
"kind": "segmentation",
"name": "iOS signups by plan",
"events": [{ "event": "Sign Up", "where": [
{ "property": "platform", "op": "is", "values": ["iOS"], "scope": "event" }
]}],
"measured_as": { "as": "unique_users" },
"group_by": [{ "property": "plan",
Compile errors
CompileChart failures come back as a 400 naming the offending field with a
fix-oriented hint — read the hint, fix that one field, and retry. Do not
rebuild the whole chart. The ones seen most in production:
- Range/interval unit mismatch —
Invalid range format: Last 3 Years … Match the relative range's unit to the interval. The message now carries
the equivalent window for your interval; paste it in. (Last 30 Days at a
weekly interval → Last 4 Weeks.)
- Invalid group property — a registry gap, not a spelling mistake. See
"When a group property is rejected" above; do not retry variants.
- Unknown operator — the valid operator list differs per chart kind; the
fix hint in the message is usually right, but for presence checks use
set
(works in every kind) rather than is not null.
Shape mismatches no longer need a retry: a bare string is accepted wherever a
single-key wrapper object is expected and vice versa, so measured_as: "event_totals", events: ["Page Viewed"], and group_by: ["country"] all
compile. Two consecutive failures on the same field means the field is not
the problem — re-check the taxonomy, or tell the user what is blocking you
rather than trying a third spelling.
When results come back empty
- Re-read every event and property name from
search_amp_data_taxonomy / get_properties, and
check the scope matches what the taxonomy returned — including
propertyType: 'group' for account-level properties.
- Widen
date_range — the window may predate instrumentation.
- Drop
segments, then where, to find which filter empties the result.
- For property aggregations, confirm the property is in the event's
group_by.
- Confirm the event is still arriving with
check_for_recent_event_ingestion.