Aggregate Promo Discounts (Messaging)
messagingmcp-aggregate_promos
Compute a statistic over promo discount depth, deduped by promo_id.
metric defaults to 'promo_depth' — the only aggregatable field — so it can
be omitted. promo_depth_type is required (depths are unitless integers — without unit
pinning the tool would mix percentages with currency amounts). Multi-buy
promotions are EXCLUDED from every aggregate: their depth carries a third
meaning (qualifying-item %, null for pure BOGO), so 'percentage' aggregates
percentage_discount promos only and 'flat' aggregates flat_discount only.
Returns a scalar value plus n_promos and n_observed (how many distinct
promos contributed, and how many of those have a non-inferred row). With
group_by or interval, returns a buckets array of those same shapes.
group_by returns AT MOST 50 buckets — the groups with the most rows behind
them — and the response does not say what it dropped: that is every group for
'promo_type', but a fraction of them for 'retailer' (hundreds even inside one
region) and 'shoot' (many thousands). To ask about a particular retailer,
filter to it rather than expecting it among the buckets.
Use for "average % off", "deepest discount", "median flat amount". For
counts or row-level retrieval use count_promos / list_promos.
Parameters
| Name | Type | Required | Constraints | Description |
|---|---|---|---|---|
statistic | string | yes | — | Statistic to compute over distinct promotions' depth. |
promo_depth_type | string | yes | — | REQUIRED. Pins the unit. 'percentage' for % off, 'flat' for currency-amount promos. Without it, percentages and currency amounts would be averaged together into a meaningless number. |
metric | string | no | default: "promo_depth" | Field to aggregate. Only 'promo_depth' (numeric discount value) exists, so the param can be omitted. |
promo_currency | string | no | — | ISO 4217 code (e.g. GBP, USD). REQUIRED when promo_depth_type='flat' — different currencies must not be averaged. Must be omitted for 'percentage' (rejected if supplied — percentages have no currency; use regions to scope a market). |
retailers | string[] | no | — | Retailer slugs to filter by, e.g. ['zara', 'h-m']. Omit for all; matches any listed retailer. Slugs match exactly and are case-sensitive, so take them from list_retailers: match the request against retailer_name, then pass that row's retailer slug and one of the regions it reports. |
regions | string[] | no | — | Regions to filter by, e.g. ['UK', 'US']. Omit for all; matches any listed region. Exact match, so use the values list_retailers reports for the retailer. |
start_date | string | no | — | Inclusive start of the active-date window (YYYY-MM-DD). |
end_date | string | no | — | Inclusive end of the active-date window (YYYY-MM-DD). |
min_depth | integer | no | — | Minimum promo_depth. Depths are unitless — a percentage or a currency amount depending on the promo type — so constrain the type to avoid mixing units. |
max_depth | integer | no | — | Maximum promo_depth. Same unit caveat as min_depth. |
has_code | boolean | no | — | True = only promos with a code, False = only without; omit for no filter. |
include_inferred | boolean | no | default: true | Include inferred promotions (default True). False = observed only. |
group_by | string | no | — | Optional categorical bucketing. Mutually exclusive with interval. 'shoot' groups by source image to surface co-promoted bundles. Returns at most 50 buckets — the groups with the most rows behind them — and does not report what it omitted; 'retailer' and 'shoot' have far more groups than that, so treat the result as a top-50, not a complete set. |
interval | string | no | — | Optional temporal bucketing on promotion_date. Exclusive with group_by. Rows are limited to [start_date, end_date], but bucket keys are calendar-aligned (week/month starts), so an edge bucket may be keyed before start_date and cover only the in-window part of its period. |
Returns
- On success: {"result": {"metric", "statistic", "promo_depth_type",
- "promo_currency", "value", "n_promos", "n_observed", "filter_echo"}} —
- the first four pin the unit the statistic is in, without which a depth is
- meaningless.
- With
group_byorinterval: {"result": {"metric", "statistic", - "promo_depth_type", "promo_currency", "buckets": [{group, value,
- n_promos, n_observed}], "filter_echo"}}.
- On failure: {"error": {code, message}}
codeisinvalid_input,upstream_unavailable,not_entitled- (authorization refused), or
internalwhen the filtered set spans too - many distinct depths to describe in one pass — retry narrower, since
min_depth/max_depth,regions,retailersandpromo_currency- each shrink that spread.
Try it
Code examples
- curl
- TypeScript
- Python
curl -s https://mcp.edited.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-06-18" \
-H "x-api-key: $MCP_API_KEY" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "messagingmcp-aggregate_promos",
"arguments": {
"statistic": "example",
"promo_depth_type": "example",
"metric": "promo_depth",
"promo_currency": "example",
"start_date": "example",
"end_date": "example",
"min_depth": 1,
"max_depth": 1,
"has_code": false,
"include_inferred": true,
"group_by": "example",
"interval": "example"
}
}
}'
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client({ name: "demo", version: "1.0.0" }, { capabilities: {} });
await client.connect(
new StreamableHTTPClientTransport(new URL("https://mcp.edited.com/mcp"), {
requestInit: { headers: { "x-api-key": process.env.MCP_API_KEY ?? "" } },
}),
);
const result = await client.callTool({
name: "messagingmcp-aggregate_promos",
arguments: {
"statistic": "example",
"promo_depth_type": "example",
"metric": "promo_depth",
"promo_currency": "example",
"start_date": "example",
"end_date": "example",
"min_depth": 1,
"max_depth": 1,
"has_code": false,
"include_inferred": true,
"group_by": "example",
"interval": "example"
},
});
import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
headers = {"x-api-key": os.environ["MCP_API_KEY"]}
async with streamablehttp_client("https://mcp.edited.com/mcp", headers=headers) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(
"messagingmcp-aggregate_promos",
{"statistic": "example", "promo_depth_type": "example", "metric": "promo_depth", "promo_currency": "example", "start_date": "example", "end_date": "example", "min_depth": 1, "max_depth": 1, "has_code": False, "include_inferred": True, "group_by": "example", "interval": "example"},
)
Input schema
{
"type": "object",
"properties": {
"statistic": {
"description": "Statistic to compute over distinct promotions' depth.",
"enum": [
"mean",
"median",
"min",
"max"
],
"type": "string"
},
"promo_depth_type": {
"description": "REQUIRED. Pins the unit. 'percentage' for % off, 'flat' for currency-amount promos. Without it, percentages and currency amounts would be averaged together into a meaningless number.",
"enum": [
"percentage",
"flat"
],
"type": "string"
},
"metric": {
"const": "promo_depth",
"default": "promo_depth",
"description": "Field to aggregate. Only 'promo_depth' (numeric discount value) exists, so the param can be omitted.",
"type": "string"
},
"promo_currency": {
"description": "ISO 4217 code (e.g. GBP, USD). REQUIRED when promo_depth_type='flat' — different currencies must not be averaged. Must be omitted for 'percentage' (rejected if supplied — percentages have no currency; use `regions` to scope a market).",
"type": "string"
},
"retailers": {
"description": "Retailer slugs to filter by, e.g. ['zara', 'h-m']. Omit for all; matches any listed retailer. Slugs match exactly and are case-sensitive, so take them from `list_retailers`: match the request against `retailer_name`, then pass that row's `retailer` slug and one of the regions it reports.",
"items": {
"type": "string"
},
"type": "array"
},
"regions": {
"description": "Regions to filter by, e.g. ['UK', 'US']. Omit for all; matches any listed region. Exact match, so use the values `list_retailers` reports for the retailer.",
"items": {
"type": "string"
},
"type": "array"
},
"start_date": {
"description": "Inclusive start of the active-date window (YYYY-MM-DD).",
"format": "date",
"type": "string"
},
"end_date": {
"description": "Inclusive end of the active-date window (YYYY-MM-DD).",
"format": "date",
"type": "string"
},
"min_depth": {
"description": "Minimum promo_depth. Depths are unitless — a percentage or a currency amount depending on the promo type — so constrain the type to avoid mixing units.",
"type": "integer"
},
"max_depth": {
"description": "Maximum promo_depth. Same unit caveat as min_depth.",
"type": "integer"
},
"has_code": {
"description": "True = only promos with a code, False = only without; omit for no filter.",
"type": "boolean"
},
"include_inferred": {
"default": true,
"description": "Include inferred promotions (default True). False = observed only.",
"type": "boolean"
},
"group_by": {
"description": "Optional categorical bucketing. Mutually exclusive with `interval`. 'shoot' groups by source image to surface co-promoted bundles. Returns at most 50 buckets — the groups with the most rows behind them — and does not report what it omitted; 'retailer' and 'shoot' have far more groups than that, so treat the result as a top-50, not a complete set.",
"enum": [
"retailer",
"promo_type",
"region",
"shoot"
],
"type": "string"
},
"interval": {
"description": "Optional temporal bucketing on promotion_date. Exclusive with group_by. Rows are limited to [start_date, end_date], but bucket keys are calendar-aligned (week/month starts), so an edge bucket may be keyed before start_date and cover only the in-window part of its period.",
"enum": [
"day",
"week",
"month"
],
"type": "string"
}
},
"required": [
"statistic",
"promo_depth_type"
]
}
Output schema
Describes both branches of the envelope — result on success, error on a refusal. Validate against this rather than pattern-matching the prose above; see Concepts → Response shape.
{
"type": "object",
"properties": {
"result": {
"type": "object",
"properties": {
"value": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Null when no matching promotion carried a depth."
},
"n_promos": {
"description": "Distinct promotions, counted exactly to 40,000 and estimated above that (within roughly half a percent). Matched promotions carrying no depth are counted here but contribute nothing, so this sits above the population behind `value` by that share plus the estimate's error.",
"type": "integer"
},
"n_observed": {
"description": "Distinct promotions, counted exactly to 40,000 and estimated above that (within roughly half a percent). The subset with a directly-observed row.",
"type": "integer"
},
"metric": {
"const": "promo_depth",
"type": "string"
},
"statistic": {
"type": "string"
},
"promo_depth_type": {
"type": "string"
},
"promo_currency": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"filter_echo": {
"properties": {
"retailers": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"regions": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null
},
"start_date": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"end_date": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"include_inferred": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null
},
"min_depth": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
},
"max_depth": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null
},
"has_code": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null
},
"statistic": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"promo_depth_type": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"promo_currency": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"group_by": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
},
"interval": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null
}
},
"type": "object"
},
"buckets": {
"description": "Under group_by this is AT MOST 50 buckets — the groups with the most rows behind them, not the deepest — with no report of what was dropped; 50 buckets is a top-50, not a complete set.",
"items": {
"properties": {
"value": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"description": "Null when no matching promotion carried a depth."
},
"n_promos": {
"description": "Distinct promotions, counted exactly to 40,000 and estimated above that (within roughly half a percent). Matched promotions carrying no depth are counted here but contribute nothing, so this sits above the population behind `value` by that share plus the estimate's error.",
"type": "integer"
},
"n_observed": {
"description": "Distinct promotions, counted exactly to 40,000 and estimated above that (within roughly half a percent). The subset with a directly-observed row.",
"type": "integer"
},
"group": {
"type": "string"
}
},
"required": [
"value",
"n_promos",
"n_observed",
"group"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"filter_echo",
"metric",
"promo_currency",
"promo_depth_type",
"statistic"
],
"allOf": [
{
"anyOf": [
{
"required": [
"filter_echo",
"metric",
"n_observed",
"n_promos",
"promo_currency",
"promo_depth_type",
"statistic",
"value"
]
},
{
"required": [
"buckets",
"filter_echo",
"metric",
"promo_currency",
"promo_depth_type",
"statistic"
]
}
]
}
]
},
"error": {
"type": "object",
"description": "Present instead of `result` when the call was rejected. Branch on `code`; never string-match `message`.",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_input",
"not_entitled",
"upstream_unavailable",
"internal"
]
},
"message": {
"type": "string"
},
"field": {
"type": "string",
"description": "Offending parameter, when the failure is attributable to one."
},
"details": {
"type": "object",
"additionalProperties": true
}
},
"required": [
"code",
"message"
]
}
},
"x-fastmcp-wrap-result": true,
"oneOf": [
{
"required": [
"result"
]
},
{
"required": [
"error"
]
}
]
}