Count Promotions (Messaging)
messagingmcp-count_promos
Count distinct promotions matching the filters, deduped by promo_id.
A "distinct promotion" is a campaign: captures of the same offer are linked to
one promo_id across time (see explain_promo_data('identity')), and each is
counted once regardless of how many captures back it. observed_count is the
subset with at least one directly-observed (non-inferred) row. With group_by
or interval, returns a buckets array of \{group, count, observed_count}.
With interval, a promotion is counted in EVERY bucket it was active in, so
bucket counts can sum to more than the unbucketed count. With
group_by='promo_type', promos with a null type (rare) fall in no bucket,
so buckets sum to slightly less than the total. filter_echo reflects what
was actually applied.
Parameters
| Name | Type | Required | Constraints | Description |
|---|---|---|---|---|
retailers | string[] | null | no | default: null | Retailer slugs to filter by, e.g. ['zara', 'h-m']. None = all; matches any listed retailer. Call list_retailers and match the requested retailer against its retailer_name, then pass the row's retailer slug and a region it covers exactly as reported there. Slugs match exactly, so a display name or wrong case matches nothing. |
regions | string[] | null | no | default: null | Regions to filter by, e.g. ['UK', 'US']. None = all; matches any listed region. list_retailers reports the exact region values for each retailer; use those, since regions match exactly. |
start_date | string | null | no | default: null | Start of date range (inclusive, YYYY-MM-DD). Filters to promos active at any point in [start_date, end_date]. |
end_date | string | null | no | default: null | End of date range (inclusive, YYYY-MM-DD). |
promo_types | string[] | null | no | default: null | Promo types to include. Allowed: 'percentage_discount', 'flat_discount', 'multi-buy'. None = all types. |
min_depth | integer | null | no | default: null | Minimum promo_depth (e.g. 20 = 20% or 20 currency units — unitless; pair with promo_types to avoid mixing units). |
max_depth | integer | null | no | default: null | Maximum promo_depth. Same unit caveat as min_depth. |
has_code | boolean | null | no | default: null | True = only promos with a code, False = only without, null = no filter. |
include_inferred | boolean | no | default: true | When True (default), count observed + inferred promotions. When False, count only directly-observed promotions. |
group_by | string | null | no | default: null | Optional categorical bucketing. Mutually exclusive with interval. 'shoot' groups by source image to surface co-promoted bundles. |
interval | string | null | no | default: null | 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 span only the in-window part of its period. |
Returns
- On success: {"count", "observed_count", "filter_echo"} or
- {"buckets": [...], "filter_echo"}.
- On failure: {"error": str}.
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-count_promos",
"arguments": {
"retailers": null,
"regions": null,
"start_date": null,
"end_date": null,
"promo_types": null,
"min_depth": null,
"max_depth": null,
"has_code": null,
"include_inferred": true,
"group_by": null,
"interval": null
}
}
}'
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-count_promos",
arguments: {
"retailers": null,
"regions": null,
"start_date": null,
"end_date": null,
"promo_types": null,
"min_depth": null,
"max_depth": null,
"has_code": null,
"include_inferred": true,
"group_by": null,
"interval": null
},
});
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-count_promos",
{"retailers": None, "regions": None, "start_date": None, "end_date": None, "promo_types": None, "min_depth": None, "max_depth": None, "has_code": None, "include_inferred": True, "group_by": None, "interval": None},
)
Input schema
{
"type": "object",
"properties": {
"retailers": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Retailer slugs to filter by, e.g. ['zara', 'h-m']. None = all; matches any listed retailer. Call `list_retailers` and match the requested retailer against its `retailer_name`, then pass the row's `retailer` slug and a region it covers exactly as reported there. Slugs match exactly, so a display name or wrong case matches nothing."
},
"regions": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Regions to filter by, e.g. ['UK', 'US']. None = all; matches any listed region. `list_retailers` reports the exact region values for each retailer; use those, since regions match exactly."
},
"start_date": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Start of date range (inclusive, YYYY-MM-DD). Filters to promos active at any point in [start_date, end_date]."
},
"end_date": {
"anyOf": [
{
"format": "date",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "End of date range (inclusive, YYYY-MM-DD)."
},
"promo_types": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Promo types to include. Allowed: 'percentage_discount', 'flat_discount', 'multi-buy'. None = all types."
},
"min_depth": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Minimum promo_depth (e.g. 20 = 20% or 20 currency units — unitless; pair with promo_types to avoid mixing units)."
},
"max_depth": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Maximum promo_depth. Same unit caveat as min_depth."
},
"has_code": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "True = only promos with a code, False = only without, null = no filter."
},
"include_inferred": {
"default": true,
"description": "When True (default), count observed + inferred promotions. When False, count only directly-observed promotions.",
"type": "boolean"
},
"group_by": {
"anyOf": [
{
"enum": [
"retailer",
"promo_type",
"region",
"shoot"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional categorical bucketing. Mutually exclusive with `interval`. 'shoot' groups by source image to surface co-promoted bundles."
},
"interval": {
"anyOf": [
{
"enum": [
"day",
"week",
"month"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"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 span only the in-window part of its period."
}
}
}