Skip to main content
Glama

Ticker: Live Event Ticket Market

Screen Events

screen_events
Read-only

Finds and ranks upcoming events by price, demand and inventory, using a predicate over the columns list_screen_columns names; the predicate input carries the grammar and the column rules. Use it for "which events" questions; use search_events to find one event by name and get_event_analytics for every number on one event.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pageNo
sortNoServer-side ranking: sorts the FULL match set by `col` (NULLS LAST, event_id tiebreak) so the page is the true top-N, not an event_id slice. Omit for default event_id ASC. Fast on the ranking primitives (`ea.price_self_z_7d_pct_rank_subcat_tte`, `ea.price_self_z_7d_xs_z_subcat_tte`, `ea.inventory_velocity_1d_xs_z_subcat_tte`, `ea.pace_z_3d_vs_14d_xs_z_subcat_tte`); sorting other columns over a broad predicate may time out; narrow the predicate.
countNoCompute the total number of matches (up to 10,000, surfaced as `total`; `countCapped` true means at least that many). Default false: counting roughly DOUBLES query cost and is usually not needed to answer with a page of events. Set true ONLY when the user's ask is about how many events match ("how many…", "count the…") or you must paginate exhaustively. When false, `total`, `totalPages` and `countCapped` are null.
limitNoRows per page. A page too large for one tool result comes back as the first rows plus `meta.truncated` and `meta.total_rows`; narrow the predicate, project fewer columns, or page, rather than repeating the call.
columnsNoSnapshot columns to return, as namespaced refs (e.g. ["ea.median_price_current","e.event_name"]). A bare namespace ("ea") returns that whole bag; ["*"] returns the full ea+e+pa snapshot. Omit for a compact default (identity + headline signals) that keeps the payload small. Request more only when you need them. Names must come from list_screen_columns, spelled exactly; an unknown one is REJECTED by name, not dropped, so fix it and call again. EVERY ROW IS ALREADY LINKED: `event_id` and `event_url` are on every row already and do not need projecting.
predicateNoA pure boolean FILTER, not an ordering. Omit or pass {} to screen the whole live universe. To surface the most interesting rows first, use the `sort` parameter; the predicate cannot express ordering. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: "ea." = event_analytics, "e." = events, "pa." = performer_analytics, "p." = performers (p.name, the act the row is about), "v." = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {"all":[{"col":"ea.price_self_z_7d_xs_z_subcat_tte","op":">=","val":2},{"col":"ea.days_to_event","op":"between","val":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {"col":"ea.last_price_snapshot_date","op":">=","val":{"days_ago":1}} means "priced within the last day" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {"days_ago": N} value form is valid on any date/timestamp column. A weekend is {"col":"e.local_date","op":"between","val":{"weekend":"this","tz":"America/New_York"}} ("next" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error. A window of event dates from today ('the next week', 'the next 30 days') is ea.days_to_event between [0, N]. NON-ADMISSION SKUs ARE ALWAYS EXCLUDED: parking and shuttle / no-admission passes (sub_category_id 75, or an event_name matching '%parking%') never appear in a screen, and a screen that asks for sub_category_id 75 alone is refused. Also screenable: marketplace demand levels/changes (ea.interest*, ea.sales*; young series, gate freshness on ea.demand_asof_date), PERFORMER-grain demand (pa.interest*, pa.sales*, pa.demand_asof_date; a separate performer-resource reading, NOT a rollup of that performer's events; filtering on it selects every event of a matching performer, so use it to find hot performers and ea.demand_* to pick among their events; gate on pa.demand_asof_date), performer popularity-rank momentum (pa.popularity_rank*; positive change = climbing), and sale-timing/ops columns: e.presale1_date and e.onsale_date (timestamptz; NULL = unknown/TBD and NULL never matches a range predicate; negative days_ago means the FUTURE: 'on sale in the next 7 days' is {"all":[{"col":"e.onsale_date","op":">=","val":{"days_ago":0}},{"col":"e.onsale_date","op":"<=","val":{"days_ago":-7}}]}) and e.monitoring_priority (monitoring priority from P1, highest, to P10, lowest, stored as 1 to 10). Count changes come as counts (ea.listings_delta_*, ea.tickets_delta_*) AND as percents (ea.listings_pct_1d/_7d, ea.tickets_pct_1d/_7d): 'listings down 10%' is ea.listings_pct_7d <= -10, never a count of 10. ea.median_price_7d_ago is the median of 7 days earlier, a past level. ea.tm_tickets_pct_3d is the primary market's 3-day percent change (-100 = none left after some 3 days ago), and e.tm_sells is true when TM sells the event as its own primary sale ('not on TM' is false); both are primary-market columns. ONLY EVENTS DATED TODAY OR LATER (UTC) ARE SCREENED, whatever active_only says: a screen whose e.local_date range ended two or more days ago is refused. ABSORPTION IS DELISTING, NOT SALES: ea.absorption_count_1d/7d, ea.absorption_rate_7d and ea.listings_added_count_* count listings that LEFT (or joined) the marketplace: a broker withdrawal, a move to another exchange, or an expiry counts the same as a sale. NEVER report them as tickets bought or as sales volume; say 'absorbed' / 'delisted'. A predicate or sort on an absorption column adds ea.absorption_data_quality, ea.absorption_grain and ea.absorption_asof_date to the projection; quote the quality and the as-of day with any absorption number. The as-of day is yesterday (UTC) for every covered event; an event without seat block ledger coverage is NULL in every absorption column, never 0. Quality ranges from 0 to 1 and reflects observation coverage and listing-tracking quality. Lower values warrant more caution when comparing activity. Published counts can decrease after revisions and can be negative when returns exceed departures.
active_onlyNoRestrict to the active event universe (status='active'). Only events dated today or later are screened either way; false adds inactive events in that window, whose rows can be frozen at the last computation or hollow (partial_row true: treat their nulls as unknown). Default true: inactive/closed events are stale and should not rank as live signals. Set false to include them.
apply_floorNoApply the economic floor (listings_current >= 25 AND median_price_current >= 40). Default true. Set false only when explicitly screening the raw universe.
diagnosticsNoExplain the result set. EMPTY RESULTS ARE EXPLAINED, NOT GUESSED AT: the block carries `rows_evaluated` (events the predicate was measured against, after the active, parking, economic floor and freshness gates), `system_gates` (how many survived each gate), `root` and a per-leg `passed`/`failed`/`null` breakdown. Read the `warnings` before answering: ALL_NULL means that column is NULL for EVERY row evaluated, so the screen is broken, NOT a real no-match; say the signal is not computed for these events and offer another column, never 'no events match'. A real no-match has legs with non-zero `passed`: loosen the thresholds. HIGH_NULL_RATE (>50% NULL): the results are real but cover part of the universe, so say so. NO_ROWS_EVALUATED: the scope flags, not the predicate, are the problem. A leg's `marginal` is how many more rows dropping that leg adds. If `diagnostics.status` is "unavailable" the events are still correct. "on_empty" (the default) runs the analysis only when this page came back with zero rows, so an empty screen is never reported as a plain 'no matches' when it is really an ALL_NULL column. true runs it always (useful to check how much of the universe a signal actually covers); false never runs it. The analysis is a second aggregate query over the whole candidate universe and adds ~5s, so don't set true routinely.on_empty
exclude_staleNoDrop stale rows: events last priced more than 3 days behind the market frontier (last_price_snapshot_date). Default true; a 4-day-old snapshot is not a live signal. Set false only when the user explicitly wants stale rows included.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / predicate / description
      Previous value: -"A pure boolean FILTER, not an ordering. Omit or pass {} to screen the whole live universe. To surface the most interesting rows first, use the `sort` parameter; the predicate cannot express ordering. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error. A window of event dates from today ('the next week', 'the next 30 days') is ea.days_to_event between [0, N]. NON-ADMISSION SKUs ARE ALWAYS EXCLUDED: parking and shuttle / no-admission passes (sub_category_id 75, or an event_name matching '%parking%') never appear in a screen, and a screen that asks for sub_category_id 75 alone is refused. Also screenable: marketplace demand levels/changes (ea.interest*, ea.sales*; young series, gate freshness on ea.demand_asof_date), PERFORMER-grain demand (pa.interest*, pa.sales*, pa.demand_asof_date; a separate performer-resource reading, NOT a rollup of that performer's events; filtering on it selects every event of a matching performer, so use it to find hot performers and ea.demand_* to pick among their events; gate on pa.demand_asof_date), performer popularity-rank momentum (pa.popularity_rank*; positive change = climbing), and sale-timing/ops columns: e.presale1_date and e.onsale_date (timestamptz; NULL = unknown/TBD and NULL never matches a range predicate; negative days_ago means the FUTURE: 'on sale in the next 7 days' is {\"all\":[{\"col\":\"e.onsale_date\",\"op\":\">=\",\"val\":{\"days_ago\":0}},{\"col\":\"e.onsale_date\",\"op\":\"<=\",\"val\":{\"days_ago\":-7}}]}) and e.monitoring_priority (monitoring priority from P1, highest, to P10, lowest, stored as 1 to 10). Count changes come as counts (ea.listings_delta_*, ea.tickets_delta_*) AND as percents (ea.listings_pct_1d/_7d, ea.tickets_pct_1d/_7d): 'listings down 10%' is ea.listings_pct_7d <= -10, never a count of 10. ea.median_price_7d_ago is the median of 7 days earlier, a past level. ea.tm_tickets_pct_3d is the primary market's 3-day percent change (-100 = none left after some 3 days ago), and e.tm_sells is true when TM sells the event as its own primary sale ('not on TM' is false); both are primary-market columns. ONLY EVENTS DATED TODAY OR LATER (UTC) ARE SCREENED, whatever active_only says: a screen whose e.local_date range ended two or more days ago is refused. ABSORPTION IS DELISTING, NOT SALES: ea.absorption_count_1d/7d, ea.absorption_rate_7d and ea.listings_added_count_* count listings that LEFT (or joined) the marketplace: a broker withdrawal, a move to another exchange, or an expiry counts the same as a sale. NEVER report them as tickets bought or as sales volume; say 'absorbed' / 'delisted'. A predicate or sort on an absorption column adds ea.absorption_data_quality, ea.absorption_grain and ea.absorption_asof_date to the projection; quote the quality and the as-of day with any absorption number. The as-of day is yesterday (UTC) for every covered event; an event without seat block ledger coverage is NULL in every absorption column, never 0. Quality ranges from 0 to 1 and reflects observation coverage and listing-tracking quality. Lower values warrant more caution when comparing activity. Published counts can decrease after revisions and can be negative when returns exceed departures."New value: +"A pure boolean FILTER, not an ordering. Omit or pass {} to screen the whole live universe. To surface the most interesting rows first, use the `sort` parameter; the predicate cannot express ordering. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. A weekend is {\"col\":\"e.local_date\",\"op\":\"between\",\"val\":{\"weekend\":\"this\",\"tz\":\"America/New_York\"}} (\"next\" for the one after): the coming Friday to Sunday in tz, from today on a Friday to Sunday, resolved on every run. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error. A window of event dates from today ('the next week', 'the next 30 days') is ea.days_to_event between [0, N]. NON-ADMISSION SKUs ARE ALWAYS EXCLUDED: parking and shuttle / no-admission passes (sub_category_id 75, or an event_name matching '%parking%') never appear in a screen, and a screen that asks for sub_category_id 75 alone is refused. Also screenable: marketplace demand levels/changes (ea.interest*, ea.sales*; young series, gate freshness on ea.demand_asof_date), PERFORMER-grain demand (pa.interest*, pa.sales*, pa.demand_asof_date; a separate performer-resource reading, NOT a rollup of that performer's events; filtering on it selects every event of a matching performer, so use it to find hot performers and ea.demand_* to pick among their events; gate on pa.demand_asof_date), performer popularity-rank momentum (pa.popularity_rank*; positive change = climbing), and sale-timing/ops columns: e.presale1_date and e.onsale_date (timestamptz; NULL = unknown/TBD and NULL never matches a range predicate; negative days_ago means the FUTURE: 'on sale in the next 7 days' is {\"all\":[{\"col\":\"e.onsale_date\",\"op\":\">=\",\"val\":{\"days_ago\":0}},{\"col\":\"e.onsale_date\",\"op\":\"<=\",\"val\":{\"days_ago\":-7}}]}) and e.monitoring_priority (monitoring priority from P1, highest, to P10, lowest, stored as 1 to 10). Count changes come as counts (ea.listings_delta_*, ea.tickets_delta_*) AND as percents (ea.listings_pct_1d/_7d, ea.tickets_pct_1d/_7d): 'listings down 10%' is ea.listings_pct_7d <= -10, never a count of 10. ea.median_price_7d_ago is the median of 7 days earlier, a past level. ea.tm_tickets_pct_3d is the primary market's 3-day percent change (-100 = none left after some 3 days ago), and e.tm_sells is true when TM sells the event as its own primary sale ('not on TM' is false); both are primary-market columns. ONLY EVENTS DATED TODAY OR LATER (UTC) ARE SCREENED, whatever active_only says: a screen whose e.local_date range ended two or more days ago is refused. ABSORPTION IS DELISTING, NOT SALES: ea.absorption_count_1d/7d, ea.absorption_rate_7d and ea.listings_added_count_* count listings that LEFT (or joined) the marketplace: a broker withdrawal, a move to another exchange, or an expiry counts the same as a sale. NEVER report them as tickets bought or as sales volume; say 'absorbed' / 'delisted'. A predicate or sort on an absorption column adds ea.absorption_data_quality, ea.absorption_grain and ea.absorption_asof_date to the projection; quote the quality and the as-of day with any absorption number. The as-of day is yesterday (UTC) for every covered event; an event without seat block ledger coverage is NULL in every absorption column, never 0. Quality ranges from 0 to 1 and reflects observation coverage and listing-tracking quality. Lower values warrant more caution when comparing activity. Published counts can decrease after revisions and can be negative when returns exceed departures."
  2. Changed5 schema fields changed
    • changedInput schema / properties / active_only / description
      Previous value: -"Restrict to the active event universe (status='active'). Default true: inactive/closed events are stale and should not rank as live signals. Set false to include them."New value: +"Restrict to the active event universe (status='active'). Only events dated today or later are screened either way; false adds inactive events in that window, whose rows can be frozen at the last computation or hollow (partial_row true: treat their nulls as unknown). Default true: inactive/closed events are stale and should not rank as live signals. Set false to include them."
    • changedInput schema / properties / columns / description
      Previous value: -"Snapshot columns to return, as namespaced refs (e.g. [\"ea.median_price_current\",\"e.event_name\"]). A bare namespace (\"ea\") returns that whole bag; [\"*\"] returns the full ea+e+pa snapshot. Omit for a compact default (identity + headline signals) that keeps the payload small. Request more only when you need them. Names must come from list_screen_columns, spelled exactly; an unknown one is rejected by name rather than dropped. `event_id` and `event_url` are on every row already and do not need projecting."New value: +"Snapshot columns to return, as namespaced refs (e.g. [\"ea.median_price_current\",\"e.event_name\"]). A bare namespace (\"ea\") returns that whole bag; [\"*\"] returns the full ea+e+pa snapshot. Omit for a compact default (identity + headline signals) that keeps the payload small. Request more only when you need them. Names must come from list_screen_columns, spelled exactly; an unknown one is REJECTED by name, not dropped, so fix it and call again. EVERY ROW IS ALREADY LINKED: `event_id` and `event_url` are on every row already and do not need projecting."
    • changedInput schema / properties / diagnostics / description
      Previous value: -"Explain the result set (see EMPTY RESULTS in the tool description). \"on_empty\" (the default) runs the analysis only when this page came back with zero rows, so an empty screen is never reported as a plain 'no matches' when it is really an ALL_NULL column. true runs it always (useful to check how much of the universe a signal actually covers); false never runs it. The analysis is a second aggregate query over the whole candidate universe and adds ~5s, so don't set true routinely."New value: +"Explain the result set. EMPTY RESULTS ARE EXPLAINED, NOT GUESSED AT: the block carries `rows_evaluated` (events the predicate was measured against, after the active, parking, economic floor and freshness gates), `system_gates` (how many survived each gate), `root` and a per-leg `passed`/`failed`/`null` breakdown. Read the `warnings` before answering: ALL_NULL means that column is NULL for EVERY row evaluated, so the screen is broken, NOT a real no-match; say the signal is not computed for these events and offer another column, never 'no events match'. A real no-match has legs with non-zero `passed`: loosen the thresholds. HIGH_NULL_RATE (>50% NULL): the results are real but cover part of the universe, so say so. NO_ROWS_EVALUATED: the scope flags, not the predicate, are the problem. A leg's `marginal` is how many more rows dropping that leg adds. If `diagnostics.status` is \"unavailable\" the events are still correct. \"on_empty\" (the default) runs the analysis only when this page came back with zero rows, so an empty screen is never reported as a plain 'no matches' when it is really an ALL_NULL column. true runs it always (useful to check how much of the universe a signal actually covers); false never runs it. The analysis is a second aggregate query over the whole candidate universe and adds ~5s, so don't set true routinely."
    • addedInput schema / properties / limit / description
      Added value: +"Rows per page. A page too large for one tool result comes back as the first rows plus `meta.truncated` and `meta.total_rows`; narrow the predicate, project fewer columns, or page, rather than repeating the call."
    • changedInput schema / properties / predicate / description
      Previous value: -"Predicate DSL object (see tool description), a pure boolean FILTER, not an ordering. Omit or pass {} to screen the whole live universe. To surface the most interesting rows first, use the `sort` parameter; the predicate cannot express ordering."New value: +"A pure boolean FILTER, not an ordering. Omit or pass {} to screen the whole live universe. To surface the most interesting rows first, use the `sort` parameter; the predicate cannot express ordering. Predicate DSL (recursive JSON): a leaf is {col, op, val}; composites are {all:[...]} (AND) or {any:[...]} (OR). Columns are prefixed: \"ea.\" = event_analytics, \"e.\" = events, \"pa.\" = performer_analytics, \"p.\" = performers (p.name, the act the row is about), \"v.\" = venues (v.venue_name, the venue's name; v.city, v.state, v.country_code, v.capacity). Ops: =, !=, >, >=, <, <=, between (val=[lo,hi]), in (val=[...]), contains (case-insensitive substring on a TEXT column; val is a plain string), not_contains (removes the rows whose TEXT column holds val as a case-insensitive substring; a row with no value stays; % and _ in val match themselves, while contains reads them as wildcards). Example: {\"all\":[{\"col\":\"ea.price_self_z_7d_xs_z_subcat_tte\",\"op\":\">=\",\"val\":2},{\"col\":\"ea.days_to_event\",\"op\":\"between\",\"val\":[7,60]}]}. NAMED ACTS / TEAMS / PERFORMERS, use p.name. When the user names the act the events are BY (a musician like 'Taylor Swift', a team like 'Lakers', a comedian, a touring show), filter the performer column: {col:'p.name', op:'contains', val:'Taylor Swift'}. p.name is the act the row is KEYED on. e.event_name is the TITLE, which reads 'A at B' for a sports event and so also returns the opponent's home games, and for a concert also returns parking and support billings. A performer or team ask NEVER goes to e.event_name. Keep writing 'contains' for an act name. When what was typed IS a whole act name in the catalog, the server rewrites that leaf to op '=' and says so; anything else stays a substring. REMOVING ROWS BY A WORD. 'NOT Little', 'not the Lakers', 'without Taylor', 'excluding Hamilton' remove every row whose title contains the word: {col:'e.event_name', op:'not_contains', val:'Little'}. Never write '!=' for a word: '!=' compares the WHOLE value, so e.event_name != 'Manilow' removes nothing. A MISSPELLED superlative is still a superlative. 'htotest', 'hotest', 'bigest movers', 'chepest', 'lowset price' read as the word they intend and take the ordering that word takes. A ranking word is NEVER a name: never put it in a 'contains' leaf on e.event_name or p.name, and never answer with sort null because the spelling was odd. ECONOMIC FLOOR. The server already ANDs ea.listings_current >= 25 AND ea.median_price_current >= 40 into every screen. Do NOT add a listings-depth floor of your own. A column whose name ends in d1 or 1d is a ONE-DAY CHANGE, not yesterday's value. 'Inventory dropping' or 'listings falling' is a SEVEN-DAY change or an absorption column, never a one-day ratio. A stale row, or an event that is not active, is NEVER a move: its numbers describe the last day it was priced. Gate a movement ask on ea.last_price_snapshot_date with a relative date. The economic floor CAN be turned off at the call site (apply_floor=false, or apply_economic_floor=false on a rule), only when you explicitly want the raw universe. A freshness gate is NOT applied by default, so supply it yourself: {\"col\":\"ea.last_price_snapshot_date\",\"op\":\">=\",\"val\":{\"days_ago\":1}} means \"priced within the last day\" and compiles to CURRENT_DATE - 1, re-evaluated every tick, where a literal date string silently rots. The {\"days_ago\": N} value form is valid on any date/timestamp column. e.event_id, e.venue_id and e.performer_id take the ids rows carry (ev..., vn..., pf...). Authoritative validation runs in SQL; an invalid predicate returns a clear error. A window of event dates from today ('the next week', 'the next 30 days') is ea.days_to_event between [0, N]. NON-ADMISSION SKUs ARE ALWAYS EXCLUDED: parking and shuttle / no-admission passes (sub_category_id 75, or an event_name matching '%parking%') never appear in a screen, and a screen that asks for sub_category_id 75 alone is refused. Also screenable: marketplace demand levels/changes (ea.interest*, ea.sales*; young series, gate freshness on ea.demand_asof_date), PERFORMER-grain demand (pa.interest*, pa.sales*, pa.demand_asof_date; a separate performer-resource reading, NOT a rollup of that performer's events; filtering on it selects every event of a matching performer, so use it to find hot performers and ea.demand_* to pick among their events; gate on pa.demand_asof_date), performer popularity-rank momentum (pa.popularity_rank*; positive change = climbing), and sale-timing/ops columns: e.presale1_date and e.onsale_date (timestamptz; NULL = unknown/TBD and NULL never matches a range predicate; negative days_ago means the FUTURE: 'on sale in the next 7 days' is {\"all\":[{\"col\":\"e.onsale_date\",\"op\":\">=\",\"val\":{\"days_ago\":0}},{\"col\":\"e.onsale_date\",\"op\":\"<=\",\"val\":{\"days_ago\":-7}}]}) and e.monitoring_priority (monitoring priority from P1, highest, to P10, lowest, stored as 1 to 10). Count changes come as counts (ea.listings_delta_*, ea.tickets_delta_*) AND as percents (ea.listings_pct_1d/_7d, ea.tickets_pct_1d/_7d): 'listings down 10%' is ea.listings_pct_7d <= -10, never a count of 10. ea.median_price_7d_ago is the median of 7 days earlier, a past level. ea.tm_tickets_pct_3d is the primary market's 3-day percent change (-100 = none left after some 3 days ago), and e.tm_sells is true when TM sells the event as its own primary sale ('not on TM' is false); both are primary-market columns. ONLY EVENTS DATED TODAY OR LATER (UTC) ARE SCREENED, whatever active_only says: a screen whose e.local_date range ended two or more days ago is refused. ABSORPTION IS DELISTING, NOT SALES: ea.absorption_count_1d/7d, ea.absorption_rate_7d and ea.listings_added_count_* count listings that LEFT (or joined) the marketplace: a broker withdrawal, a move to another exchange, or an expiry counts the same as a sale. NEVER report them as tickets bought or as sales volume; say 'absorbed' / 'delisted'. A predicate or sort on an absorption column adds ea.absorption_data_quality, ea.absorption_grain and ea.absorption_asof_date to the projection; quote the quality and the as-of day with any absorption number. The as-of day is yesterday (UTC) for every covered event; an event without seat block ledger coverage is NULL in every absorption column, never 0. Quality ranges from 0 to 1 and reflects observation coverage and listing-tracking quality. Lower values warrant more caution when comparing activity. Published counts can decrease after revisions and can be negative when returns exceed departures."
  3. Changed1 schema field changed
    • removedInput schema / properties / limit / maximum
      Removed value: -100
  4. First observed

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnlyHint/destructiveHint/openWorldHint), but the description adds substantial behavior beyond them: the auto-applied economic floor, mandatory exclusion of parking/no-admission SKUs and refusal of sub_category_id 75, the today-or-later freshness scope, absorption = delisting not sales, cost implications of count (~2x) and diagnostics (~5s), and SQL-side validation with clear errors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded correctly (purpose and alternatives first), but the body is a single unstructured wall of DSL rules that in places restates what the predicate schema parameter already documents. For a 10-parameter DSL tool length is defensible, yet the lack of grouping and the duplication keep it from earning full marks.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a complex recursive predicate, 10 parameters and no output schema, the description covers the gates, exclusions, error semantics, cost tradeoffs and the performer-vs-event-name distinction, so an agent has what it needs to call the tool correctly without a return-value contract.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 90%, so the baseline is 3; the description adds meaning by routing act/team asks to p.name rather than e.event_name, warning that '!=' compares whole values while not_contains removes by word, and clarifying the {days_ago}/{weekend} relative value forms. Much of this overlaps the predicate schema text, so it is additive but not fully independent of structured data.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb+resource+scope: 'Finds and ranks upcoming events by price, demand and inventory' built on a predicate over the columns list_screen_columns names. It also explicitly distinguishes itself from siblings, routing 'which events' questions here, name lookups to search_events, and single-event metrics to get_event_analytics.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit routing: 'Use it for which events questions; use search_events ... get_event_analytics ...'. It also carries conditional usage guidance for individual flags (count only for cardinality asks, diagnostics only on empty, apply_floor false only for the raw universe, exclude_stale false only on explicit request), which is when-to-use at parameter granularity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources