Skip to content

Search tools reference ​

The Product (commerce) MCP server exposes 10 read-only tools your AI sales agent uses to search, filter, and fetch a merchant's catalog. Inside MyChatBot these run automatically whenever the agent answers a product question β€” you don't call them by hand. You can also point an external MCP client (Claude Code, Cursor, or any MCP host) at the same tools to build your own sales agent on top of your catalog.

This page is the cheat sheet: every tool, what it does, what it accepts, and β€” crucially β€” what each filter operator means. All ten operators are enforced on every filter-accepting tool, including semantic_product_search; the remaining skill is picking the operator that matches the intent. Reach for it whenever you're writing a search prompt or debugging why a filter didn't match what you expected.

All 10 tools live behind one MCP server URL, scoped to a single account + product integration:

https://product.mychatbot.app/mcp/{account_id}/{integration_id}/stream

In the app β€” get the connect command

You don't assemble the URL by hand. Open the Sales Platform's Knowledge Base (https://app.mychatbot.app/knowledge-base) or Agents β†’ Connectors (https://app.mychatbot.app/agents/connectors), find your product integration card, and choose Connect AI tools. MyChatBot fills in the correct account + integration and hands you a copy-paste setup command for Claude Code plus a Cursor config. See Product (commerce) MCP for the full walkthrough.

Cheat sheet ​

All 10 tools at a glance ​

ToolWhat it doesKey inputsFilter operators
semantic_product_searchNatural-language + visual catalog search, ranked by meaningquery and/or image_url; optional limit=20, filters[]all ten β€” eq, ne, gt, gte, lt, lte, in, contains, startswith, endswith
filter_category_productsDeterministic browse + filtering inside one known categorycategory_id; optional filters[], limit=100all ten
get_all_categoriesReturns the full category treeβ€”β€”
get_product_detailsFull record for one productproduct_idβ€”
get_product_variantsA product's sizes / colors / other variantsproduct_idβ€”
find_similar_products"More like this" / cross-sell from a productproduct_id; optional limit=10β€”
get_category_attributesWhich attributes a category hascategory_idβ€”
get_category_attribute_valuesAn attribute's type + its common valuescategory_id, attribute_nameβ€”
get_available_filtersFacet values + product counts per value (for faceted UIs)category_id; optional filters[], facet_attributes[]all ten
get_products_by_idsExact batch fetch by known IDsproduct_ids[]β€”

semantic_product_search has no required input β€” pass at least a query or an image_url.

What each operator means ​

All three filter-accepting tools enforce every operator with the same semantics β€” a filter is never silently ignored, on semantic_product_search included. Pick by intent:

OperatorMeaningReach for it when
eqExact value match, or an exact element of a list-shaped attribute (native list or comma-separated string; element match is case-insensitive)Categorical facets (brand, color) and list membership β€” coverage lists like cities within radius
inOR of eq across the listed values"Any of these exact values/elements"
neThe exact negation of eq β€” excludes the exact value and any case-insensitive list element"Everything except X"
gt, gte, lt, lteNumeric comparison whenever both sides read as numbers β€” a JSON number and a numeric string like "30" behave identically, even on attributes stored as text. A range means the attribute exists and compares: products lacking the attribute, and values that don't read as numbers against a numeric bound, are excludedPrice and other numeric ranges β€” the stored values must read as plain numbers (decorated values like "1 200 Π³Ρ€Π½" don't match)
containsCase-insensitive literal substring in a string field β€” matches inside wordsPartial names and other genuine substring lookups; never list membership
startswith, endswithCase-sensitive literal prefix/suffix of the whole stored string (comma lists are not split; on a native list any element may match)Code/name prefixes where case is known

Match the operator to the attribute's shape

contains matches inside words ("Neath" matches Rosneath) β€” use eq/in for list membership. startswith/endswith are case-sensitive and test the whole stored string, not comma-separated elements β€” use contains when case or position is uncertain. Range operators compare numerically whenever the stored values read as numbers β€” even on attributes indexed as text β€” but a decorated value like "1 200 Π³Ρ€Π½" has no numeric reading and cannot be compared; check what the values look like with get_category_attribute_values first.

Which tool for which job ​

JobTool
Natural-language / conversational lookup ("gift for dry skin")semantic_product_search
Visual search ("find this dress")semantic_product_search with image_url
Rank by meaning but constrain by attributessemantic_product_search with filters[]
Match a partial product name or other string fragmentsemantic_product_search with contains, or filter_category_products when the category is known
Free-text query + price / numeric rangesemantic_product_search with gte/lte filters (see the example below)
Deterministic browse + range filtering inside one known categoryfilter_category_products
List the catalog's category treeget_all_categories
Full record for one product (agent already has the ID)get_product_details
Sizes / colors / other variants of a productget_product_variants
"More like this" / cross-sell from a productfind_similar_products
Discover what attributes a category hasget_category_attributes
Discover a specific attribute's type + common valuesget_category_attribute_values
Build a faceted UI / count how many products per valueget_available_filters
Exact batch lookup by known IDs (e.g. SKUs already resolved)get_products_by_ids

Defaults ​

InputApplies toDefault
limitsemantic_product_search20 (max 60)
limitfilter_category_products100
limitfind_similar_products10
enable_rerankingsemantic_product_searchtrue
truncate_descriptionsemantic_product_searchtrue

Passing filters ​

Every entry in filters[] (for semantic_product_search, filter_category_products, and get_available_filters) has three parts:

json
{ "attribute": "price", "operator": "gte", "value": 500 }
  • attribute β€” the attribute name to constrain on (e.g. price, vendor, stickers). The reserved attribute name targets the product's top-level name.
  • operator β€” eq, ne, gt, gte, lt, lte, in, contains, startswith, endswith. Every operator carries the same semantics on all three filter-accepting tools (see What each operator means above).
  • value β€” a scalar for most operators; a list when the operator is in; a nonblank string when it is contains. For range operators a JSON number and a numeric string (50 or "50") behave identically.

Multiple filters are AND-composed β€” every filter must match. In semantic_product_search they apply across the whole ranked search.

contains is case-insensitive, applies only to string fields, and treats its value as a literal substring β€” it matches inside words, so contains "Neath" matches Rosneath. For example, name contains ASHEN matches The Ashen Evening Dress. Characters such as * and ? are ordinary text, not wildcard syntax.

eq matches the exact attribute value, or an exact element of a list-shaped attribute β€” a native list field such as stickers, or a comma-separated string such as a cities within radius coverage list. Full-string equality is exact; the element match is case-insensitive and ignores surrounding whitespace, so eq "rosneath" matches a list containing Rosneath while eq "Neath" does not (Neath is not an element). in behaves as OR of eq across its values β€” "has any of these exact values/elements", with the same case-insensitive element matching.

ne is the exact negation of eq: it excludes a product when the stored value equals the filter value or contains it as an exact (case-insensitive) list element β€” so country ne "great britain" excludes a stored Great Britain.

startswith / endswith are case-sensitive literal prefix/suffix tests against the whole stored string. A comma-separated value is not split into elements (startswith "Alexandria" does not match Airdrie, Alexandria, …); on a native list attribute such as stickers, any element may carry the prefix/suffix. When case or position is uncertain, prefer contains.

Range operators (gt, gte, lt, lte) compare numerically whenever both sides read as numbers β€” a JSON number and a numeric string like "150" behave identically, and this holds even when the attribute is stored as text (a radius attribute holding "50" range-filters numerically). Genuinely textual pairs compare lexicographically. A range means the attribute exists and compares: products that lack the attribute do not match, and neither do values with no numeric reading against a numeric bound (a decorated value such as "1 200 Π³Ρ€Π½" is excluded, not passed through). On a native list attribute, any element may satisfy the comparison.

Membership is eq's job, never contains's

contains is a substring match: on a coverage list containing Rosneath, contains "Neath" wrongly matches. Use eq (or in) to test list membership; keep contains for genuine partial-text lookups.

Range and prefix filters are enforced on semantic_product_search too

Every operator is applied as a hard constraint on the ranked results β€” a filter is never silently dropped. One nuance: only eq/in/contains also guide candidate discovery on a whole-catalog search; the other operators purely constrain what comes back. So keep the descriptive intent in query, and check with get_category_attribute_values that a range-filtered attribute holds values that read as plain numbers.

Example tool calls ​

These show how to drive the tools over MCP. Each is a tools/call with a tool name and its arguments.

Semantic search with an attribute filter ​

Rank by meaning, but only among products carrying a specific sticker:

json
{
  "name": "semantic_product_search",
  "arguments": {
    "query": "gift for dry skin",
    "limit": 20,
    "filters": [
      { "attribute": "stickers", "operator": "eq", "value": "ΠŸΠΎΠ΄Π°Ρ€ΡƒΠ½ΠΎΠΊ" }
    ]
  }
}

stickers is a string-list attribute, so an eq matches any single element in the list. This is the sweet spot for semantic_product_search filters: eq/in on a string attribute.

Partial product-name filter ​

Match a product even when the shopper supplies only part of its name:

json
{
  "name": "semantic_product_search",
  "arguments": {
    "query": "evening dress",
    "filters": [
      { "attribute": "name", "operator": "contains", "value": "ASHEN" }
    ]
  }
}

This match is case-insensitive and searches the top-level product name. The same filter can narrow filter_category_products or get_available_filters when you also supply their required category_id.

Free-text query with a price range ​

Range filters are enforced directly on semantic_product_search β€” one call combines free-text ranking with a hard numeric constraint:

json
{
  "name": "semantic_product_search",
  "arguments": {
    "query": "waterproof hiking boots",
    "limit": 20,
    "filters": [
      { "attribute": "price", "operator": "gte", "value": 80 },
      { "attribute": "price", "operator": "lte", "value": 200 }
    ]
  }
}

The bounds may be JSON numbers or numeric strings (80 and "80" behave identically), and the comparison is numeric even when the attribute is stored as text β€” the only hard requirement is that the stored values read as plain numbers; a feed that shipped price as decorated text ("1 200 Π³Ρ€Π½") cannot be range-compared (get_category_attribute_values shows what the values look like). When you'd rather browse a whole category deterministically than rank by meaning, the classic pattern still applies: resolve a category_id from the returned categories[], then run the same range filters through filter_category_products.

json
{
  "name": "semantic_product_search",
  "arguments": {
    "image_url": "https://example.com/customer-photo.jpg",
    "limit": 10
  }
}

Multi-value in filter ​

json
{
  "name": "semantic_product_search",
  "arguments": {
    "query": "running shoes",
    "filters": [
      { "attribute": "brand", "operator": "in", "value": ["Nike", "Adidas", "Asics"] }
    ]
  }
}

Deterministic category browse with a price range ​

json
{
  "name": "filter_category_products",
  "arguments": {
    "category_id": "shoes-running",
    "filters": [
      { "attribute": "price", "operator": "gte", "value": 50 },
      { "attribute": "price", "operator": "lte", "value": 150 }
    ],
    "limit": 100
  }
}

Introspect a category before filtering ​

json
{ "name": "get_category_attributes",        "arguments": { "category_id": "shoes-running" } }
{ "name": "get_category_attribute_values",  "arguments": { "category_id": "shoes-running", "attribute_name": "brand" } }
{ "name": "get_available_filters",          "arguments": { "category_id": "shoes-running", "facet_attributes": ["brand", "color"] } }

Exact batch fetch by ID ​

json
{
  "name": "get_products_by_ids",
  "arguments": { "product_ids": ["SKU-1001", "SKU-1002", "SKU-1003"] }
}

Best practices ​

Do

  • Start conversational queries with semantic_product_search; it understands the meaning of the query and reranks the best matches to the top.
  • Pick operators by intent: eq/in for exact values and list membership (native lists and comma-separated attributes such as coverage lists), contains for partial string matching, gt/gte/lt/lte for numeric ranges, ne to exclude a value/element, startswith/endswith for case-sensitive prefixes/suffixes.
  • Discover valid attribute names/values with get_category_attributes and get_category_attribute_values before sending filters β€” don't guess attribute names, and confirm range-filtered attributes hold values that read as plain numbers.
  • Use filter_category_products when the category is known and you want a deterministic category browse rather than ranked recall.
  • Use get_products_by_ids (or get_product_details) when the agent already resolved exact IDs β€” skip ranking entirely.
  • Keep enable_reranking=true for natural-language queries; it materially improves ordering.

Don't

  • Don't use contains on numeric or boolean fields β€” it requires a nonblank string field and value β€” and never use it for list membership: it matches inside words (contains "Neath" matches Rosneath); use eq/in instead.
  • Don't treat contains as wildcard syntax β€” * and ? are matched literally (the same holds for startswith/endswith values).
  • Don't reach for startswith/endswith when case or position is uncertain β€” they are case-sensitive and test the whole stored string; contains is the forgiving option.
  • Don't range-filter attributes whose values don't read as plain numbers ("1 200 Π³Ρ€Π½") β€” those pairs can't be compared numerically. Numeric bounds may be numbers or numeric strings; both behave identically.
  • Don't request a limit above 60 for semantic_product_search.
  • Don't send filters to tools that don't accept them (find_similar_products, get_product_details, get_all_categories, etc.).

Reranking is auto-skipped for code / URL queries

Even with enable_reranking=true, reranking is automatically skipped when the query looks like a SKU/barcode or a URL β€” MyChatBot tunes exactly when it kicks in. For exact SKU-style lookups, prefer get_products_by_ids, or set enable_reranking=false.

truncate_description trims payloads

truncate_description defaults to true, so semantic_product_search results carry shortened descriptions. Set it to false only when the agent needs the full text. Very long descriptions are shortened when indexed regardless.

Test it ​

  • Call get_all_categories, pick a category_id, then chain get_category_attributes β†’ get_category_attribute_values to confirm which attributes and values are actually filterable for that catalog β€” and whether a range-target like price holds values that read as plain numbers.
  • Run a known-good query through semantic_product_search and assert the expected product IDs appear.
  • Verify a price range on semantic_product_search directly (products outside the bound must not appear), and cross-check the same filter through filter_category_products β€” both enforce the same contract.
  • If an attribute filter silently returns nothing, check the operator/attribute pairing first (numeric bound on a string attribute? case-sensitive prefix?); otherwise the integration's searchable attributes may be out of date β€” re-index by removing and re-adding the product integration, or ask support to run a full re-index.

See also ​