Example workflow
A worked example showing how an agent uses EDITED MCP end-to-end. The user asks a fuzzy retail question; the agent uses MCP tools to resolve fuzzy concepts to stable IDs, then feeds those IDs into the market-data tools to fetch the answer — all over the same MCP connection.
The scenario
"Compare dress assortments at Zara and H&M in the UK over the last quarter."
To answer this, the agent needs three things from the EDITED catalogue:
- Stable retailer slugs for "Zara" and "H&M" — and only the UK storefronts
- A product-category ID for "dresses"
- A market ID for the UK market
It can't guess these. They live in EDITED's taxonomy, behind MCP.
One unwrapper, used at every step
Every tool returns exactly one of result or error, so a single helper covers
all four calls below. A refusal arrives with isError: false, which is why this
checks the payload and not just the flag — see
Concepts → Response shape.
function unwrap(result) {
if (result.isError) {
// never completed — the text is prose, not JSON
throw new Error(result.content?.[0]?.text ?? "call failed");
}
const body = result.structuredContent;
if (body.error) throw new Error(`${body.error.code}: ${body.error.message}`);
return body.result;
}
Step 1 — Resolve the retailers
const result = await client.callTool({
name: "mdmcp-search_retailers",
arguments: {
query: ["Zara", "H&M"], // batch — both at once
search_limit: 3,
country_code: "UK",
},
});
// search_retailers wraps its hits in an object, so a batch gives you one
// wrapper per query — not a bare list of retailers.
const [zaraResult, hmResult] = unwrap(result);
// → zaraResult: { retailers: [...], search_limit: 3, at_search_limit: false }
// → zaraResult.retailers[0]: { name: "Zara (UK)", slug: "zara", region: { iso_code: "UK", ... }, ... }
// → hmResult.retailers[0]: { name: "H&M (UK)", slug: "hm", region: { iso_code: "UK", ... }, ... }
const retailers = [zaraResult.retailers[0].slug, hmResult.retailers[0].slug];
// → ["zara", "hm"]
The agent picks the top match for each query. Note we batch both queries in a single call — faster, fewer round-trips, less context burned.
Two things to notice, because they bite everyone once. search_retailers is
the odd one out among the resolvers: it returns a retailers wrapper, while
search_brands, search_markets and search_product_searches return a bare
list. And the slug is not the name with a country suffix — Zara in the UK is
zara, not zara-uk. Always read the slug you were given rather than
constructing one.
Step 2 — Resolve the product category
const result = await client.callTool({
name: "mdmcp-search_product_searches",
arguments: { query: "dresses", search_limit: 3 },
});
const categories = unwrap(result);
// → categories[0]: { id: 138, name: "Dresses", category: "dresses", vertical: "apparel", ... }
const categoryId = categories[0].id;
Step 3 — Resolve the market
const result = await client.callTool({
name: "mdmcp-search_markets",
arguments: { query: "United Kingdom", search_limit: 1 },
});
const markets = unwrap(result);
// → markets[0]: { id: "UK", name: "United Kingdom (UK)" }
const marketId = markets[0].id;
Search the full country name, not the abbreviation. query: "UK" with
search_limit: 1 comes back with Ukraine — a two-letter query is thin material
for a vector search, and the one result you asked for isn't the one you meant.
Asking for a few candidates and picking the match you recognise is the more
robust habit generally.
Step 4 — Query the data
With three resolved identifiers, the agent runs the comparison with
market_data_table — another MCP call, so there's no separate API to wire up.
The resolved slug and id values go straight in as filters:
const result = await client.callTool({
name: "mdmcp-market_data_table",
arguments: {
name: "average price for dresses at Zara and H&M in the UK",
metrics: ["avg_price", "product_count"],
start_date: "2026-04-01",
end_date: "2026-06-30",
filters: [
{ field: "retailer", op: "in", value: retailers }, // ["zara", "hm"] (step 1)
{ field: "product_searches", op: "in", value: [categoryId] }, // 138 (step 2)
{ field: "market", op: "in", value: [marketId] }, // "UK" (step 3)
],
group_by: [{ field: "retailer" }],
},
});
const data = unwrap(result);
// → {
// columns: ["Retailer", "Average Price ($) [avg/period]", "Product Count [avg/period]"],
// rows: [["H&M (UK)", 44.07, 4774.67], ["Zara (UK)", 51.99, 1277.67]],
// total_row_count: 2, currency: "USD", vertical: "apparel",
// granularity: "M", evaluated_window: "2026-04-01:2026-06-30", ...
// }
return data; // ← what the agent presents to the user
Every resolved identifier goes in as a filter, not as a top-level argument —
filters is a list of { field, op, value } triples, and at least one is
required. name is a free-text label for the table you're asking for, not an
identifier. Dates are explicit start_date / end_date within the last two
years; there is no date_range shorthand. The full grammar — every field,
metric and operator — is in mdmcp-market_data_docs, one topic per call.
See How to query → Picking the right tool
for the full market_data_table grammar — metrics, group-by, histograms,
trends, and period-over-period comparisons.
Why resolve IDs first?
You could ship every retailer slug, category ID, and market ID into the agent's system prompt. That doesn't scale — EDITED has thousands of each, and they change weekly.
Resolving on demand lets the agent look them up the same way a junior analyst would: ask "what's the slug for Zara UK?", get the answer, then query the data — every step over the same MCP connection. The agent stays small and current; the catalogue stays in EDITED's source of truth.
Patterns to steal
- Batch parallel resolutions. When the agent needs two or three lookups
to answer one question, batch them in a single
tools/callwith a list of queries. EDITED MCP runs them concurrently. - Always pick the top match for fuzzy queries. Vector search ranks by semantic similarity. The first hit is usually right; if it isn't, your query was ambiguous.
- Log the slugs/IDs the agent resolves into. That's what you'll need when reproducing a session for debugging.